Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Authentication Flow (Internal)

This page documents the detailed internal mechanics of authentication in Aero2, covering both the OIDC Provider and Relying Party flows.

OIDC Provider Flow

When Aero2 acts as an OIDC Provider, it issues tokens to registered OAuth clients on behalf of authenticated users.

Client App                   Aero2 Worker                    D1 Database
    |                             |                               |
    |  GET /oauth2/authorize      |                               |
    |  ?client_id=...             |                               |
    |  &redirect_uri=...         |                               |
    |  &response_type=code        |                               |
    |  &scope=openid profile      |                               |
    |  &code_challenge=...        |                               |
    |  &code_challenge_method=S256|                               |
    |  &state=...                 |                               |
    |  &nonce=...                 |                               |
    |---------------------------->|                               |
    |                             |  Validate params              |
    |                             |  - client_id exists?          |
    |                             |  - redirect_uri allowed?      |
    |                             |  - PKCE S256 required         |
    |                             |  - scopes valid?              |
    |                             |-------------------------------->|
    |                             |  Check active session          |
    |                             |<--------------------------------|
    |                             |                               |
    |     302 -> /login           |  No session: redirect to login |
    |<----------------------------|                               |
    |                             |                               |
    |  (user authenticates)       |                               |
    |                             |                               |
    |                             |  Generate auth code            |
    |                             |  Store code + PKCE + nonce    |
    |                             |-------------------------------->|
    |                             |                               |
    |  302 -> redirect_uri        |                               |
    |  ?code=AUTH_CODE            |                               |
    |  &state=...                 |                               |
    |<----------------------------|                               |
    |                             |                               |
    |  POST /oauth2/token         |                               |
    |  grant_type=authorization_code                              |
    |  code=AUTH_CODE             |                               |
    |  code_verifier=...          |                               |
    |  client_id=...              |                               |
    |  client_secret=...          |                               |
    |---------------------------->|                               |
    |                             |  Verify client credentials     |
    |                             |  Verify PKCE (S256)           |
    |                             |  Delete code (atomic)         |
    |                             |-------------------------------->|
    |                             |                               |
    |                             |  Sign JWT with Durable Object  |
    |                             |  (JWKS key management)        |
    |                             |                               |
    |  200 {                      |                               |
    |    access_token: "...",     |                               |
    |    id_token: "...",         |                               |
    |    refresh_token: "...",    |                               |
    |    token_type: "Bearer",    |                               |
    |    expires_in: 3600         |                               |
    |  }                          |                               |
    |<----------------------------|                               |

Key Implementation Details

  • File: src/backend/op.ts
  • Auth codes are consumed atomically using DELETE ... RETURNING to prevent replay
  • PKCE with S256 is mandatory; plain method is rejected
  • Tokens are signed using the JWKS Durable Object (src/backend/jwks.ts)
  • Access tokens include token_use: "access", ID tokens include token_use: "id"
  • Refresh tokens are HMAC-hashed before storage in D1
  • The nonce claim is included in ID tokens when provided in the authorize request

Relying Party Flow

When Aero2 acts as a Relying Party, it delegates authentication to an external identity provider (GitHub, Google, or a custom OIDC/OAuth2 provider).

User Browser              Aero2 Worker                 External IdP        D1
    |                          |                            |               |
    |  GET /rp/authorize/github|                            |               |
    |---------------------------->|                          |               |
    |                          |  Generate state + PKCE     |               |
    |                          |  Store state in D1         |               |
    |                          |----------------------------------------------->|
    |                          |  Set state cookie (HttpOnly, SameSite=Lax)    |
    |                          |                            |               |
    |  302 -> github.com/      |                            |               |
    |  login/oauth/authorize   |                            |               |
    |  ?client_id=...          |                            |               |
    |  &state=...              |                            |               |
    |  &scope=user:email       |                            |               |
    |<----------------------------|                          |               |
    |                          |                            |               |
    |  (user authenticates     |                            |               |
    |   at GitHub)             |                            |               |
    |                          |                            |               |
    |  GET /rp/callback/github |                            |               |
    |  ?code=GITHUB_CODE       |                            |               |
    |  &state=...              |                            |               |
    |---------------------------->|                          |               |
    |                          |  Verify state              |               |
    |                          |  - Match state param       |               |
    |                          |    to state cookie         |               |
    |                          |  - Atomic consume from D1  |               |
    |                          |----------------------------------------------->|
    |                          |                            |               |
    |                          |  Exchange code for tokens  |               |
    |                          |--------------------------->|               |
    |                          |  {access_token, id_token}  |               |
    |                          |<---------------------------|               |
    |                          |                            |               |
    |                          |  Fetch user info           |               |
    |                          |--------------------------->|               |
    |                          |  {email, name, picture}    |               |
    |                          |<---------------------------|               |
    |                          |                            |               |
    |                          |  Create or link user       |               |
    |                          |  - Find by email + app_id  |               |
    |                          |  - Create if not exists    |               |
    |                          |  - Link identity           |               |
    |                          |----------------------------------------------->|
    |                          |                            |               |
    |                          |  Create session            |               |
    |                          |  - Generate session JWT    |               |
    |                          |  - Store in user_sessions  |               |
    |                          |----------------------------------------------->|
    |                          |                            |               |
    |  302 -> /                |                            |               |
    |  Set-Cookie: session=JWT |                            |               |
    |  (HttpOnly, Secure,      |                            |               |
    |   SameSite=Strict)       |                            |               |
    |<----------------------------|                          |               |

Key Implementation Details

  • File: src/backend/rp.ts
  • State is stored in both D1 and an HttpOnly cookie for double-bind CSRF protection
  • State is consumed atomically: DELETE FROM oauth_state WHERE state = ? AND idp_name = ? AND expires_at > datetime('now') RETURNING redirect_uri
  • PKCE code verifier is generated for providers that support it
  • User creation/linking is done in a single flow: look up by email, create if new, add identity link
  • External IdP tokens (access/refresh) are encrypted with AES-256-GCM before storage

Session Creation

Both flows result in session creation:

  1. Generate a session JWT signed by the JWKS Durable Object
  2. Session JWT includes: sub (user ID), iat, exp, token_use: "session", auth_time
  3. Store session metadata in user_sessions table (token hash, IP, user agent, expiry)
  4. Set the session JWT as an HttpOnly, Secure, SameSite=Strict cookie

Token Verification

When a protected endpoint receives a request:

  1. Extract token from Authorization: Bearer <token> header or session cookie
  2. Fetch the JWKS from the Durable Object (cached with ETag)
  3. Verify the RS256 signature against the JWKS
  4. Validate standard claims: iss (issuer), aud (audience), exp (not expired)
  5. Validate token_use claim matches expected type (session, access, or id)
  6. Look up session in D1 to verify it has not been revoked
  7. Look up user to verify account is not disabled
  8. Attach user and session objects to the Hono request context

Key Files

FileResponsibility
src/backend/op.tsOIDC Provider: authorize, token, userinfo, revoke endpoints
src/backend/rp.tsRelying Party: authorize redirect, callback, user creation
src/backend/middleware/auth.tsToken verification, session validation, RBAC checks
src/backend/utils/token.tsJWT signing (via DO), verification, claims building
src/backend/utils/crypto.tsHMAC hashing, AES-256-GCM encryption, PBKDF2
src/backend/jwks.tsDurable Object: key generation, rotation, JWKS endpoint