# Code-Server Authentication Flow: Password vs. Hashed Password Options Explained

> Explore code-server authentication flows. Understand plain vs hashed password validation, including Argon2 and SHA-256 methods. Secure your setup.

- Repository: [Coder/code-server](https://github.com/coder/code-server)
- Tags: deep-dive
- Published: 2026-03-01

---

**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`](https://github.com/coder/code-server/blob/main/src/node/main.ts), the startup logic validates that at least one credential source is present:

```typescript
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`](https://github.com/coder/code-server/blob/main/src/node/cli.ts) reads `$PASSWORD` and `$HASHED_PASSWORD` from the environment, prioritizing the hashed variant if both are set:

```typescript
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`](https://github.com/coder/code-server/blob/main/src/node/routes/login.ts), the server calls `handlePasswordValidation` from [`src/node/util.ts`](https://github.com/coder/code-server/blob/main/src/node/util.ts):

```typescript
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:

```typescript
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:

```typescript
res.cookie(req.cookieSessionName, hashedPassword, getCookieOptions(req))

```

### Subsequent Request Validation

For every protected request, the `authenticated` middleware in [`src/node/http.ts`](https://github.com/coder/code-server/blob/main/src/node/http.ts) extracts the cookie and validates it against the stored plain password using `isCookieValid`:

```typescript
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:

```typescript
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`](https://github.com/coder/code-server/blob/main/src/node/util.ts) inspects the hash string for the `$argon` marker:

```typescript
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:

```typescript
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:

```typescript
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

```bash
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

```bash

# 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

```bash

# 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

```bash

# 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.ts`](https://github.com/coder/code-server/blob/main/src/node/main.ts) enforces password presence at startup, while [`src/node/cli.ts`](https://github.com/coder/code-server/blob/main/src/node/cli.ts) prioritizes `$HASHED_PASSWORD` over `$PASSWORD` if 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 `$argon` string) and legacy SHA-256; cookies are compared directly to stored hashes using `safeCompare`.
- **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`](https://github.com/coder/code-server/blob/main/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.