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-tokento 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_activeuser_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.