Code-Server Authentication Flow: Password vs. Hashed Password Options Explained
Code-server supports two authentication modes: plain passwords (hashed with Argon2 at login) and pre-hashed passwords (Argon2 or legacy SHA-256), with different validation paths for each.
The coder/code-server repository implements a flexible authentication system to secure the web-based VS Code interface. Understanding the code-server authentication flow helps administrators choose between supplying a plain text password (processed at runtime) or a pre-hashed password (verified directly), with each method employing distinct session management strategies.
How Code-Server Determines the Authentication Mode
Startup Validation in main.ts
When launching with --auth password, the server strictly requires either a plain password or a hashed password. In src/node/main.ts, the startup logic validates that at least one credential source is present:
if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
throw new Error(
"Please pass in a password via the config file or environment variable ($PASSWORD or $HASHED_PASSWORD)",
)
}
This check ensures the server never starts in password authentication mode without credentials configured.
Environment Variable Parsing in cli.ts
The CLI layer in src/node/cli.ts reads $PASSWORD and $HASHED_PASSWORD from the environment, prioritizing the hashed variant if both are set:
let usingEnvPassword = !!process.env.PASSWORD
if (process.env.PASSWORD) { args.password = process.env.PASSWORD }
const usingEnvHashedPassword = !!process.env.HASHED_PASSWORD
if (process.env.HASHED_PASSWORD) {
args["hashed-password"] = process.env.HASHED_PASSWORD
usingEnvPassword = false
}
Notice that usingEnvPassword is explicitly set to false when HASHED_PASSWORD is detected, ensuring the UI displays the correct authentication source.
Plain Text Password Authentication Flow
Login Processing and Argon2 Hashing
When using the PASSWORD environment variable or --password flag, the server stores the plain text value only in memory. Upon receiving a login POST request in src/node/routes/login.ts, the server calls handlePasswordValidation from src/node/util.ts:
const password = sanitizeString(req.body?.password)
const hashedPasswordFromArgs = req.args["hashed-password"]
const passwordMethod = getPasswordMethod(hashedPasswordFromArgs)
const { isPasswordValid, hashedPassword } = await handlePasswordValidation({
passwordMethod,
hashedPasswordFromArgs,
passwordFromRequestBody: password,
passwordFromArgs: req.args.password,
})
For the PLAIN_TEXT method, handlePasswordValidation validates the submitted password against the configured plain password, then generates a fresh Argon2 hash for the session cookie:
switch (passwordMethod) {
case "PLAIN_TEXT": {
// Validate against args.password, then hash for cookie
hashedPassword = await hash(passwordFromRequestBody)
}
// ...
}
Session Cookie Generation
After successful validation, the server writes the Argon2 hash (not the plain password) into the session cookie:
res.cookie(req.cookieSessionName, hashedPassword, getCookieOptions(req))
Subsequent Request Validation
For every protected request, the authenticated middleware in src/node/http.ts extracts the cookie and validates it against the stored plain password using isCookieValid:
const passwordMethod = getPasswordMethod(hashedPasswordFromArgs)
const isCookieValidArgs: IsCookieValidArgs = {
passwordMethod,
cookieKey: sanitizeString(req.cookies[req.cookieSessionName]),
passwordFromArgs: req.args.password || "",
hashedPasswordFromArgs,
}
return await isCookieValid(isCookieValidArgs)
In the PLAIN_TEXT path, isCookieValid uses isHashMatch to verify the cookie's Argon2 hash against the original plain password:
switch (passwordMethod) {
case "PLAIN_TEXT": isValid = await isHashMatch(passwordFromArgs, cookieKey)
// ...
}
Pre-Hashed Password Authentication Flow
Hash Algorithm Detection
When using HASHED_PASSWORD, the server must determine if the hash uses Argon2 or legacy SHA-256. The getPasswordMethod function in src/node/util.ts inspects the hash string for the $argon marker:
export function getPasswordMethod(hashedPassword?: string): PasswordMethod {
if (!hashedPassword) return "PLAIN_TEXT"
if (hashedPassword.includes("$argon")) return "ARGON2"
return "SHA256"
}
Argon2 vs SHA-256 Validation Paths
For ARGON2 hashes, the login handler uses isHashMatch to verify the submitted password against the stored hash. For SHA256 (legacy) hashes, it uses isHashLegacyMatch. Both methods return the original stored hash to be placed directly in the cookie:
switch (passwordMethod) {
case "ARGON2": {
isValid = await isHashMatch(hashedPasswordFromArgs, passwordFromRequestBody)
hashedPassword = hashedPasswordFromArgs
}
case "SHA256": {
isValid = isHashLegacyMatch(hashedPasswordFromArgs, passwordFromRequestBody)
hashedPassword = hashedPasswordFromArgs
}
}
Cookie Validation for Pre-Hashed Modes
Unlike the plain text flow, subsequent requests with pre-hashed passwords use constant-time string comparison rather than re-hashing. The isCookieValid function uses safeCompare to match the cookie against the stored hash:
switch (passwordMethod) {
case "ARGON2":
case "SHA256":
isValid = safeCompare(cookieKey, hashedPasswordFromArgs)
}
Key Implementation Differences
Plain text password (PASSWORD): The server never stores the raw password persistently; it only keeps it in memory during runtime. At login, the submitted password is hashed with Argon2 and stored in the cookie. Subsequent requests validate by hashing the stored plain password and comparing it to the cookie value using isHashMatch.
Pre-hashed password (HASHED_PASSWORD): The server stores the exact hash you provide (either Argon2 or SHA-256). At login, the submitted password is verified against this hash using isHashMatch or isHashLegacyMatch. The cookie contains the pre-hashed value directly, and subsequent requests use safeCompare for constant-time equality checking against the stored hash.
Practical Configuration Examples
Starting with a Plain Text Password
export PASSWORD="mySecret123"
code-server --auth password
The server hashes the password with Argon2 upon first login and stores that hash in the session cookie.
Starting with an Argon2 Pre-Hashed Password
# Generate hash first
HASH=$(node -e "require('argon2').hash('mySecret123').then(h=>process.stdout.write(h))")
export HASHED_PASSWORD=$HASH
code-server --auth password
The cookie will contain the exact Argon2 hash you provided, validated via safeCompare.
Using a Legacy SHA-256 Hash
# Generate with: echo -n "mySecret123" | sha256sum
export HASHED_PASSWORD="936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af"
code-server --auth password
Absence of the $argon marker triggers the SHA256 validation path.
Logging in via cURL
# Obtain session cookie
curl -c cookies.txt -d "password=mySecret123" http://localhost:8080/login
# Access protected resources
curl -b cookies.txt http://localhost:8080/
Summary
- Mode detection:
src/node/main.tsenforces password presence at startup, whilesrc/node/cli.tsprioritizes$HASHED_PASSWORDover$PASSWORDif both are set. - Plain text flow: Login triggers Argon2 hashing for the cookie; subsequent requests re-hash the stored password to validate the cookie using
isHashMatch. - Pre-hashed flow: Supports both Argon2 (detected by
$argonstring) and legacy SHA-256; cookies are compared directly to stored hashes usingsafeCompare. - Security: Plain text mode never persists the password to disk; pre-hashed mode allows storing credentials in configuration files without exposing raw passwords.
Frequently Asked Questions
What happens if I set both PASSWORD and HASHED_PASSWORD environment variables?
The CLI parser in src/node/cli.ts explicitly sets usingEnvPassword = false when HASHED_PASSWORD is detected, giving the hashed password precedence. The server will ignore $PASSWORD and use the pre-hashed value exclusively.
How does code-server handle legacy SHA-256 hashed passwords?
The getPasswordMethod function checks for the absence of the $argon marker. If the hash does not contain this string, the server treats it as a legacy SHA-256 hash and uses isHashLegacyMatch for validation during login and safeCompare for cookie validation.
Where is the authentication state stored between requests?
The server stores an Argon2 hash (for plain text mode) or the exact pre-hash (for hashed mode) in an HTTP-only session cookie named via req.cookieSessionName. The server never writes the plain text password to the cookie or persistent storage.
Can I switch from plain password to hashed password without downtime?
Yes. You can generate an Argon2 hash of your current password, stop code-server, replace PASSWORD with HASHED_PASSWORD in your environment or config file, and restart. The authentication flow will seamlessly transition to the pre-hashed validation path without requiring users to change their login credentials.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →