auth

package
v0.0.0-...-9827620 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

README

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:

    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 /loginLoginHandler: Initiate authorization (PKCE + OIDC).
  • GET /callbackCallbackHandler: Handle authorization code, exchange tokens, verify ID token, provision user and create session.
  • GET /logoutLogoutHandler: Initiate signout at the IdP; sets a logout_state and redirects the user to the IdP logout endpoint.
  • GET /logout-callbackLogoutCallbackHandler: Verify state at signout return, clear the session, and redirect to /login.
  • GET /registerRegistrationHandler: 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)
  • valkey-addr — Valkey/Redis address for the server-side session store (default localhost:6379)
  • envproduction or other (controls the cookie Secure flag)

  • Session store: scs (alexedwards/scs) with a Valkey/Redis backend (redisstore) — sessions are server-side, so no signing/encryption secret is needed
  • 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
    • Usernamepreferred_username
    • Emailemail

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: <client-secret> (optional for public clients)
  • base-url: http://localhost:8080
  • valkey-addr: localhost:6379
  • 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 valkey-addr points at your session store.

  • 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.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	SessionManager *scs.SessionManager
	OAuthConfig    *oauth2.Config
	Verifier       *oidc.IDTokenVerifier
	Provider       *oidc.Provider
	Database       *sql.DB              // Raw DB for transactions (auto-provisioning)
	IdentityQ      identity.Querier     // Identity module queries
	OrgQ           organization.Querier // Organization module queries
}

Config holds all auth-related configuration

func Setup

func Setup(database *sql.DB, identityQ identity.Querier, orgQ organization.Querier) (*Config, error)

Setup initializes the auth configuration

func (*Config) CallbackHandler

func (c *Config) CallbackHandler(w http.ResponseWriter, r *http.Request)

CallbackHandler processes the OIDC callback

func (*Config) GetOrgID

func (c *Config) GetOrgID(ctx context.Context) string

GetOrgID returns the active organization UUID.

func (*Config) GetPersonID

func (c *Config) GetPersonID(ctx context.Context) string

GetPersonID returns the person UUID of the authenticated user. Returns empty string if the user is not authenticated.

func (*Config) GetUserEmail

func (c *Config) GetUserEmail(ctx context.Context) string

GetUserEmail returns the email of the authenticated user.

func (*Config) GetUserName

func (c *Config) GetUserName(ctx context.Context) string

GetUserName returns the display name of the authenticated user.

func (*Config) GetUserSession

func (c *Config) GetUserSession(ctx context.Context) *UserSession

GetUserSession retrieves the authenticated user's session data. Returns nil if the user is not authenticated.

func (*Config) GetUsername

func (c *Config) GetUsername(ctx context.Context) string

GetUsername returns the username of the authenticated user.

func (*Config) GetWorkspaceID

func (c *Config) GetWorkspaceID(ctx context.Context) string

GetWorkspaceID returns the active workspace UUID.

func (*Config) HasRole

func (c *Config) HasRole(r *http.Request, role string) bool

HasRole checks if the current user has the specified role.

func (*Config) IsAuthenticated

func (c *Config) IsAuthenticated(ctx context.Context) bool

IsAuthenticated returns true if the user has an active session.

func (*Config) LoginHandler

func (c *Config) LoginHandler(w http.ResponseWriter, r *http.Request)

LoginHandler initiates the OIDC authentication flow

func (*Config) LogoutCallbackHandler

func (c *Config) LogoutCallbackHandler(w http.ResponseWriter, r *http.Request)

LogoutCallbackHandler handles the redirect after Keycloak logout

func (*Config) LogoutHandler

func (c *Config) LogoutHandler(w http.ResponseWriter, r *http.Request)

LogoutHandler handles user logout

func (*Config) Middleware

func (c *Config) Middleware(selfAuthenticatedPaths ...string) func(http.Handler) http.Handler

Middleware returns an auth middleware function. selfAuthenticatedPaths are integration-declared endpoints that verify their own caller (provider webhooks checking an HMAC signature) — they must bypass session auth or the provider's delivery bounces off the /login redirect. The server passes the paths collected from RouteMount.CSRFExemptPaths: a path that authenticates by signature is exempt from both protections for the same reason.

func (*Config) RegisterHandlers

func (c *Config) RegisterHandlers(mux *http.ServeMux)

RegisterHandlers adds all auth-related handlers to the router

func (*Config) RegistrationHandler

func (c *Config) RegistrationHandler(w http.ResponseWriter, r *http.Request)

RegistrationHandler redirects to the OIDC registration page

type UserSession

type UserSession struct {
	PersonID    string
	OrgID       string
	WorkspaceID string
	OIDCSubject string
	Email       string
	Name        string
	Username    string
	Roles       []string
}

UserSession contains the authenticated user's session data. This provides type-safe access to session values.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL