# Authentication (internal/auth) This package implements an OAuth 2.0 Authorization Code Grant with PKCE flow, enhanced by OpenID Connect (OIDC) for user identity. > Purpose: Document the implementation details, configuration variables, endpoints, and security considerations for the OIDC authentication flow implemented by the `internal/auth` package. --- ## Overview The `internal/auth` package uses the Authorization Code grant combined with PKCE (Proof Key for Code Exchange) to authenticate users against an OpenID Connect (OIDC) identity provider (IdP). This flow is secure and recommended for both public and confidential clients as it prevents authorization code interception attacks. The codebase integrates with Keycloak (or another OIDC-compatible IdP) and performs the following responsibilities: - Initiate authorization using PKCE and OIDC `nonce` and `state` parameters - Exchange authorization code for a token using the code verifier - Verify the ID token (signature, nonce, issuer, audience) - Provision or update a local user record in the database - Manage a session using secure cookies - Initiate and verify single sign-out via the IdP --- ## Grant Type - OAuth 2.0: Authorization Code Grant - PKCE extension: RFC 7636 (SHA-256-based `S256` method) - OIDC: Identity layer using `openID` scope + ID token verification --- ## Key Implementation Details ### PKCE Flow - A random `code_verifier` is generated for each login attempt: `codeVerifier := generateRandomString(32)` - The `code_challenge` is computed by taking the SHA-256 hash of the code verifier and encoding it via base64url without padding: ```go hashVal := sha256.Sum256([]byte(codeVerifier)) codeChallenge := base64.RawURLEncoding.EncodeToString(hashVal[:]) ``` - The `code_challenge` and `code_challenge_method=S256` are added to the initial authorization URL. - When exchanging the authorization code for tokens, the previously generated `code_verifier` is supplied using `oauth2.VerifierOption(codeVerifier)`. ### OIDC and Security Parameters - `state`: A CSRF token is generated and validated to prevent CSRF attacks. - `nonce`: A token to prevent replay attacks. The nonce is validated against the `nonce` claim in the ID token. - ID Token (`id_token`) verification is performed using the OIDC provider's public keys via the `Verifier`. - If the ID token is missing or invalid, the flow is rejected. --- ## Endpoints The `internal/auth` RegisterHandlers method hooks the following HTTP routes on the `*http.ServeMux`: - `GET /login` — `LoginHandler`: Initiate authorization (PKCE + OIDC). - `GET /callback` — `CallbackHandler`: Handle authorization code, exchange tokens, verify ID token, provision user and create session. - `GET /logout` — `LogoutHandler`: Initiate signout at the IdP; sets a `logout_state` and redirects the user to the IdP logout endpoint. - `GET /logout-callback` — `LogoutCallbackHandler`: Verify `state` at signout return, clear the session, and redirect to `/login`. - `GET /register` — `RegistrationHandler`: Redirects to the IdP registration path (or the auth endpoint with registration params) using `code_challenge` and `nonce` similarly to `/login`. --- ## Configuration & Environment Variables The auth package depends on the application configuration (via `viper`) to initialize. Required config keys (from `viper`): - `oidc-idp-issuer-url` — Identity Provider issuer URL (e.g., `https://idp.example.com/realms/realm`) - `oidc-sp-client-id` — OAuth client ID (service provider client identifier) - `oidc-sp-client-secret` — OAuth client secret (if using a confidential client; optional for public clients) - `base-url` — Application base redirect URL (e.g., `https://app.example.com`) - `session-secret` — Session encryption key (used to initialize `sessions.NewCookieStore`) - `env` — `production` or other (controls the cookie `Secure` flag) --- ## Session & Cookie Management - Session store: `gorilla/sessions` CookieStore (`Store: *sessions.CookieStore`) - Cookie options: - `HttpOnly: true` — prevents JS access to cookie - `Secure: true` only when `env` is `production` - `SameSite: Lax` — helps prevent CSRF while allowing top-level navigation - `MaxAge: 7 days` by default - The session stores these keys: - `state`, `nonce`, `code_verifier` during the authentication initiation - `authenticated`, `id_token`, `user_db_id`, `oidc_subject`, `email`, `name`, `username` after authentication - `logout_state` during sign-out --- ## DB Integration - The code uses the `db.Querier` interface (SQLC-generated queries) to get, create, or update users: - `GetUserByOIDCSubject(ctx, subject)` — find existing user by `sub` claim - `CreateUser(ctx, CreateUserParams)` — create new user when not found - `UpdateUser(ctx, UpdateUserParams)` — update user info on subsequent logins - When a new user logs in for the first time, the implementation creates a new database user record with relevant OIDC claims: - `OidcSubject` — OIDC `sub` claim - `Username` — `preferred_username` - `Email` — `email` --- ## Logout Flow - `LogoutHandler` builds a sign-out URL to the IdP's logout endpoint (Keycloak-style URL shown in code) and sets a `logout_state` in session for verification on return. - If available, `id_token_hint` is included in the logout request to help the IdP identify the session. - `LogoutCallbackHandler` validates the `state`, then clears the session by setting `session.Options.MaxAge = -1` and saving it. --- ## Quick Start: Configuration Example Example `viper`/env config for a development environment: - `oidc-idp-issuer-url`: `https://idp.example.com/realms/demo` - `oidc-sp-client-id`: `member-console` - `oidc-sp-client-secret`: `` (optional for public clients) - `base-url`: `http://localhost:8080` - `session-secret`: `replace-with-a-random-secret` - `env`: `development` Once configured, the app can be started and these endpoints will be active on the configured host. --- ## References - OAuth 2.0 Authorization Framework (RFC 6749) - Proof Key for Code Exchange (PKCE) (RFC 7636) - OpenID Connect Core 1.0 --- ## Notes & Tips - PKCE is mandatory for public clients (e.g., SPA, mobile) and recommended for confidential clients to protect against code interception attacks. - The `nonce` is stored in session and validated against the ID token `nonce` claim as an additional replay protection. - If using a different IdP, you may need to adjust registration and logout endpoint paths since some providers expose different routes for registration and logout. - When deploying to production, ensure `base-url` reflects your secure domain and `session-secret` is kept secret. --- ## Related code files - `internal/auth/auth.go` — implementation of the handlers and flow - `internal/auth/` — this README and other auth related files - `internal/db/*` — database schema & SQLC queries (`GetUserByOIDCSubject`, `CreateUser`, `UpdateUser`) --- If you want, I can also create short usage examples (cURL) to simulate login and logout sequences or add inline code examples for environment setup and deployment.