auth-flows

star 121

Detailed documentation of the 4 authentication systems, token lifecycle, middleware pipeline, session management, and RBAC authorization.

gjovanovicst By gjovanovicst schedule Updated 3/4/2026

name: auth-flows description: Detailed documentation of the 4 authentication systems, token lifecycle, middleware pipeline, session management, and RBAC authorization. license: MIT

Authentication Systems Overview

The project has 4 independent authentication systems:

System Mechanism Middleware File Context Keys Set
User JWT Bearer token internal/middleware/auth.go userID, appID, roles, sessionID
Admin API Key Static env + DB keys internal/middleware/admin_auth.go auth_type = "admin"
App API Key DB-backed per-app keys internal/middleware/app_api_key.go auth_type = "app"
Admin GUI Session HTTP-only cookie + Redis internal/middleware/gui_auth.go admin_id, admin_username, admin_session_id

1. User JWT Authentication

Token Lifecycle

Access Token:

  • Generated at login/social-login/2FA-verify/passkey-login/magic-link-verify
  • Contains: UserID, AppID, SessionID, TokenType="access", Roles[]
  • Expiry: 15 min default (configurable via ACCESS_TOKEN_EXPIRATION_MINUTES)
  • Validated by AuthMiddleware() on every protected request

Refresh Token:

  • Generated alongside access token
  • Contains: UserID, AppID, SessionID, TokenType="refresh"
  • Expiry: 720 hours default (configurable via REFRESH_TOKEN_EXPIRATION_HOURS)
  • Used via POST /refresh-token to get a new access+refresh pair
  • Old refresh token is rotated (new one issued, session updated)

Token Blacklisting (Redis):

  • Individual access token blacklisting on logout
  • User-wide token blacklisting on password change
  • Session existence check (if session revoked, token rejected)

AuthMiddleware Flow (internal/middleware/auth.go)

1. Extract "Authorization" header, strip "Bearer " prefix
2. Parse JWT via jwt.ParseToken()
3. Reject if TokenType is "refresh" (prevents refresh token misuse)
4. Check Redis: is this specific access token blacklisted?
5. Check Redis: are ALL tokens for this user blacklisted?
6. If claims.SessionID exists: check session still exists in Redis
7. Set context: userID, appID, roles, sessionID

Login Flow (standard password login)

1. POST /login with email + password
2. userHandler.Login -> userService.Login
3. Verify password with bcrypt
4. Check if 2FA is enabled for user
   - If YES: return temp 2FA token, require POST /2fa/login-verify
   - If NO: generate access+refresh tokens, create session, return tokens
5. On success: activity log "USER_LOGIN"

2FA Login Flow

1. User logs in, gets temp 2FA token
2. POST /2fa/login-verify with temp token + code (TOTP/email/recovery)
   OR POST /2fa/passkey/begin + /finish for passkey 2FA
3. Verify code against:
   - TOTP: pquerna/otp library validation
   - Email: 6-digit code stored in Redis (10 min TTL)
   - Recovery: bcrypt-hashed codes stored in user record
   - Passkey: WebAuthn assertion ceremony
4. On success: generate real access+refresh tokens, create session

Social Login Flow

1. GET /auth/{provider}/login -> redirect to OAuth provider
2. Provider redirects to GET /auth/{provider}/callback
3. Exchange code for tokens, fetch user profile
4. Find or create user (match by provider+provider_user_id+app_id)
5. Generate access+refresh tokens, create session
6. If 2FA enabled: same 2FA flow as above

Magic Link Flow

1. POST /magic-link/request with email
2. Generate single-use token, store in Redis (10 min TTL)
3. Send email with magic link URL
4. POST /magic-link/verify with token
5. Validate token from Redis, delete after use
6. Generate access+refresh tokens, create session

Passkey Passwordless Flow

1. POST /passkey/login/begin -> return WebAuthn challenge
2. Client performs assertion with authenticator
3. POST /passkey/login/finish with assertion response
4. Validate credential, look up user
5. Generate access+refresh tokens, create session

2. Admin API Key Authentication

AdminAuthMiddleware Flow (internal/middleware/admin_auth.go)

1. Read X-Admin-API-Key header
2. Fast path: compare against ADMIN_API_KEY env var (constant-time)
3. Fallback: SHA-256 hash the key, look up in api_keys table
   - Validate key_type == "admin"
   - Async update last_used_at
4. Set context: auth_type = "admin"

Key Format

  • Prefix: ak_ + 24 random bytes base64-encoded
  • Storage: SHA-256 hash in DB, prefix (8 chars) + suffix (4 chars) for display
  • Generation: internal/admin/apikey_util.go

3. Per-Application API Key Authentication

AppApiKeyMiddleware Flow (internal/middleware/app_api_key.go)

1. Read X-App-API-Key header
2. Read app_id from context (set by AppIDMiddleware)
3. SHA-256 hash the key, look up in api_keys table
4. Validate key_type == "app"
5. Validate key.AppID matches context app_id (cross-app prevention)
6. Async update last_used_at
7. Set context: auth_type = "app"

AppRouteGuardMiddleware (internal/middleware/app_route_guard.go)

Additional guard that ensures the :id URL parameter matches the X-App-ID header, preventing URL manipulation attacks on /app/:id/* routes.

4. Admin GUI Session Authentication

GUIAuthMiddleware Flow (internal/middleware/gui_auth.go)

1. Read "admin_session" cookie
2. Call sessionValidator.ValidateSession(sessionID) -> *AdminAccount
3. If invalid: clear cookie, redirect to /gui/login
4. Set context: admin_id, admin_username, admin_session_id

Admin Login Flow

1. POST /gui/login with username + password
2. accountService.Authenticate(username, password) via bcrypt
3. If 2FA enabled:
   - Create pending 2FA session in Redis
   - Redirect to /gui/2fa-verify
   - POST /gui/2fa-verify with code
   - Promote pending session to full session
4. If no 2FA:
   - Create full session in Redis
   - Set admin_session cookie (HttpOnly, SameSite=Strict, Secure)

Admin Sessions (Redis)

  • Stored in Redis (not JWT-based)
  • Session ID is a UUID stored in cookie
  • Session data: admin account reference
  • CSRF token generated per session
  • Separate from user sessions

CSRF Protection (internal/middleware/csrf.go)

Safe methods (GET/HEAD/OPTIONS):
  -> Generate CSRF token, store in context for template rendering

State-changing methods (POST/PUT/DELETE):
  -> Read token from X-CSRF-Token header (HTMX) OR _csrf form field
  -> Validate against session's stored token
  -> 403 on mismatch

RBAC Authorization

Permission Model

Permissions are resource:action pairs (e.g., user:read, settings:write).

AuthorizePermission Middleware (internal/middleware/auth.go)

1. Read userID and appID from context
2. Call rbacService.HasPermission(appID, userID, resource, action)
3. RBAC service checks Redis cache first, then DB
4. Cache key: rbac:{appID}:{userID}
5. Cache TTL: matches access token TTL
6. Returns 403 on failure

Default Roles (seeded per new app)

Role Permissions
admin All permissions
member Read + limited write
viewer Read-only

Permission Resources Used

user, settings, log -- used with actions read, write, delete.

Session Management

User Sessions (internal/session/service.go)

Redis storage:

  • session:{appID}:{sessionID} -- hash with user_id, refresh_token, ip, user_agent, created_at, last_active
  • user_sessions:{appID}:{userID} -- set of session IDs

Lifecycle:

  • Created at login (with refresh token)
  • Refreshed on token rotation (new refresh token stored)
  • Revoked individually or all-at-once
  • Max sessions enforced (oldest evicted)

Token Blacklisting (internal/redis/redis.go)

  • blacklist:access:{tokenHash} -- individual token blacklist (TTL = remaining token life)
  • blacklist:user:{userID} -- user-wide blacklist (TTL = access token TTL)

AppID Extraction (internal/middleware/app_id.go)

1. Skip for /swagger, /admin, /gui paths
2. Read X-App-ID header
3. Fallback: app_id query parameter
4. Skip for /auth/*/callback (OAuth state carries app_id)
5. Parse as UUID, set in context
Default: 00000000-0000-0000-0000-000000000001

Shared Interfaces (web/context_keys.go)

type SessionValidator interface {
    ValidateSession(sessionID string) (*models.AdminAccount, error)
    GenerateCSRFToken(sessionID string) (string, error)
    ValidateCSRFToken(sessionID, token string) bool
}

type ApiKeyValidator interface {
    FindActiveKeyByHash(keyHash string) (*models.ApiKey, error)
    UpdateApiKeyLastUsed(id uuid.UUID)
}

When To Use This Skill

Load this skill when working on authentication, authorization, middleware, token handling, session management, or any security-related feature.

Install via CLI
npx skills add https://github.com/gjovanovicst/golang-auth-api --skill auth-flows
Repository Details
star Stars 121
call_split Forks 18
navigation Branch main
article Path SKILL.md
More from Creator
gjovanovicst
gjovanovicst Explore all skills →