This file provides guidance to Claude Code when working with code in this repository.
AuthGate is an OAuth 2.0 authorization server built with Go and Gin. It supports:
- Device Authorization Grant (RFC 8628) - For CLI tools, IoT devices, and headless environments
- Authorization Code Flow (RFC 6749) with PKCE (RFC 7636) - For web and mobile applications
Enables secure authentication without embedding client secrets.
# Build (IMPORTANT: make generate is required before building)
make generate # Generate templ templates and Swagger docs (REQUIRED before build)
make build # Build to bin/authgate with version info in LDFLAGS
# Run
./bin/authgate -v # Show version information
./bin/authgate -h # Show help
./bin/authgate server # Start the OAuth server
# Development
make dev # Hot reload development mode (watches .go, .templ, .env, .css, .js)
make generate # Compile .templ templates to Go code (REQUIRED)
make swagger # Generate OpenAPI/Swagger documentation
# Test & Lint
make test # Run tests with coverage report (outputs coverage.txt)
make coverage # View test coverage in browser
make lint # Run golangci-lint (auto-installs if missing)
make fmt # Format code with golangci-lint fmt
# Cross-compile (outputs to release/<os>/<arch>/)
make build_linux_amd64 # CGO_ENABLED=0 for static binary
make build_linux_arm64 # CGO_ENABLED=0 for static binary
# Clean
make clean # Remove bin/, release/, coverage.txt, generated templ files
# Docker
docker build -f docker/Dockerfile -t authgate .- CLI calls
POST /oauth/device/code→ receives device_code + user_code + verification_uri - User visits
/devicein browser, must login first if not authenticated - User submits user_code via
POST /device/verify→ device code marked as authorized - CLI polls
POST /oauth/tokenwith device_code every 5s → receives access_token + refresh_token - When access_token expires, CLI uses
grant_type=refresh_tokento get new token
In addition to Device Code Flow, AuthGate supports Authorization Code Flow with PKCE for web and mobile applications:
- App redirects user to
GET /oauth/authorizewith client_id, redirect_uri, state, code_challenge (PKCE) - User logs in (if needed) and sees consent screen at
/oauth/authorize - User approves →
POST /oauth/authorize→ redirects to redirect_uri with authorization code - App exchanges code for tokens via
POST /oauth/token(with code_verifier for PKCE, or client_secret for confidential clients)
User Consent Management
- Users can review granted apps at
/account/authorizations - Each authorization grants specific scopes to one app
- Users can revoke per-app access (revokes all associated tokens)
- Admins can force re-authentication for all users of a client via
/admin/clients/:id/revoke-all
Client Types
- Confidential clients: Web apps with server-side backends that can securely store client_secret
- Public clients: SPAs and mobile apps that use PKCE instead of client_secret
- Web Framework: Gin - Fast HTTP router
- Templates: templ - Type-safe Go templates (requires
make generatebefore build) - ORM: GORM - Database abstraction
- Database: SQLite (default) / PostgreSQL
- Sessions: Encrypted cookies with gin-contrib/sessions
- JWT: golang-jwt/jwt
- API Documentation: Swagger/OpenAPI via swaggo/swag
- Hot Reload: air for development (
make dev)
main.go- Wires up store → auth providers → token providers → services → handlersinternal/config/- Environment configuration managementinternal/store/- GORM-based data access layer (SQLite/PostgreSQL), uses map-based driver factoryinternal/auth/- Authentication providers (local, HTTP API, OAuth providers)internal/token/- Token provider (local JWT)internal/services/- Business logic layer (user, device, authorization, token, client, audit services)internal/handlers/- HTTP request handlers for all endpointsinternal/models/- GORM database models (User, OAuthApplication, UserAuthorization, DeviceCode, AuthorizationCode, AccessToken, OAuthConnection, AuditLog)internal/middleware/- Gin middleware (auth, CSRF, rate limiting, metrics auth, CORS)internal/metrics/- Prometheus metrics collection and caching (supports memory, Redis, Redis-aside)internal/cache/- Cache implementations (memory, Redis, Redis-aside)internal/client/- HTTP client with exponential backoff retryinternal/templates/- templ templates (_.templ→_templ.goviamake generate)internal/util/- Utility functions (crypto, context helpers)internal/version/- Version information injected at build time
Key Model Details:
OAuthApplication- Supports both Device Flow and Auth Code Flow with per-client toggles (EnableDeviceFlow,EnableAuthCodeFlow)OAuthApplication.ClientType- "confidential" (with secret) or "public" (PKCE only)UserAuthorization- Per-app consent grants (one record per user+app pair)AccessToken- Unified storage for both access and refresh tokens (distinguished bytoken_categoryfield)User.IsActive- Boolean (defaulttrue) controlling whether a user may log in. Disabling revokes all of the user's tokens andRequireAuthclears any live session on the next request. Guards prevent self-disable and disabling the last active admin.OAuthConnection- Per-user binding to a third-party provider (GitHub, Gitea, Microsoft). Stores provider tokens andLastUsedAt; admins can list/unlink them per user via/admin/users/:id/connections.
Pluggable Authentication
- Supports local (database) and external HTTP API authentication
- Per-user authentication routing based on
auth_sourcefield (hybrid mode) - Configured via
AUTH_MODEenv var (localorhttp_api) - Default admin user always uses local auth as failsafe
Token Provider
- LocalTokenProvider: Uses golang-jwt/jwt library with configurable signing algorithm
HS256(default): Symmetric HMAC-SHA256 usingJWT_SECRETRS256: Asymmetric RSA signing with PEM private key (JWT_PRIVATE_KEY_PATH)ES256: Asymmetric ECDSA P-256 signing with PEM private key (JWT_PRIVATE_KEY_PATH)
- JWKS endpoint at
/.well-known/jwks.jsonexposes public keys for RS256/ES256
Refresh Tokens (RFC 6749)
- Two modes: Fixed (reusable) and Rotation (one-time use)
- Unified storage: Both access and refresh tokens in
AccessTokentable withtoken_categoryfield - Token family tracking via
parent_token_idfor audit trails - Status management:
active,disabled, orrevoked - Enable rotation mode via
ENABLE_TOKEN_ROTATION=true
Rate Limiting
- IP-based rate limiting with configurable per-endpoint limits
- Two storage backends: Memory (single instance) or Redis (multi-pod)
- Built on github.com/ulule/limiter/v3 with sliding window algorithm
- Protects: /login (5 req/min), /oauth/device/code (10 req/min), /oauth/token (20 req/min), /device/verify (10 req/min)
- Enable/disable via
ENABLE_RATE_LIMITenv var
Audit Logging
- Tracks authentication, device authorization, token operations, admin actions, security events
- Asynchronous batch writes (every 1s or 100 records) for minimal performance impact
- Automatic sensitive data masking (passwords, tokens, secrets)
- Event types: AUTHENTICATION**, DEVICE_CODE*_, TOKEN__, CLIENT_*, USER_* (
USER_CREATED,USER_DISABLED,USER_ENABLED,USER_PASSWORD_RESET,USER_ROLE_CHANGED),OAUTH_CONNECTION_DELETED, RATE_LIMIT_EXCEEDED - Severity levels: INFO, WARNING, ERROR, CRITICAL
- Web interface at
/admin/auditwith filtering and CSV export
OAuth Provider Support
- Microsoft Entra ID (Azure AD), GitHub, Gitea
- Auto-registration of users (configurable via
OAUTH_AUTO_REGISTER) - Uses OAuth 2.0 authorization code flow
Service-to-Service Authentication
- Three modes:
none(default),simple(shared secret),hmac(signature-based) - Protects communication with external auth/token APIs
- HMAC mode provides replay attack protection with timestamp validation
Prometheus Metrics
- Comprehensive metrics for OAuth flows, authentication, tokens, sessions, HTTP requests, and database queries
- Gauge metrics (active tokens, device codes, sessions) updated at configurable intervals (default: every 5 minutes)
- Multi-replica consideration: Gauge updates query global database counts. In multi-instance deployments:
- Recommended: Enable
METRICS_GAUGE_UPDATE_ENABLEDon only one instance to avoid duplicate values - Alternative: Enable on all instances, use
max()oravg()aggregation in PromQL (notsum())
- Recommended: Enable
- Counters and histograms are safe in multi-replica setups (each instance tracks its own activity)
- Metrics cache support to reduce database load:
- Memory cache (default): Single-instance deployments, zero external dependencies
- Redis cache: Multi-instance deployments (2-5 pods), shared cache across instances using rueidis
- Redis-aside cache: High-load deployments (5+ pods), uses rueidisaside for client-side caching with RESP3 automatic invalidation
- Cache TTL matches update interval to ensure consistency and reduce database queries by 90%+ in multi-instance setups
- Implementation uses rueidisaside library correctly for cache-aside pattern with client-side caching support
- Memory considerations for redis-aside: Client-side cache size is configurable via
METRICS_CACHE_SIZE_PER_CONN(default: 32MB). Total memory usage iscache_size * number_of_connections. Rueidis typically maintains ~10 connections (based on GOMAXPROCS), so default usage is ~320MB. Adjust this value based on your deployment's memory constraints and cache hit requirements.
- Device codes expire after 30min (configurable via
DeviceCodeExpiration) - User codes: 8-char uppercase alphanumeric, normalized (uppercase + dashes removed)
- JWTs signed with configurable algorithm (HS256/RS256/ES256), configurable expiry (
JWT_EXPIRATION, default: 10 hours) with optional jitter (JWT_EXPIRATION_JITTER, default: 30 minutes) - Sessions: encrypted cookies (gin-contrib/sessions), configurable expiry (default: 1 hour), with idle timeout (default: 30 minutes) and fingerprinting (User-Agent validation)
- Polling interval: 5 seconds
- Templates and static files embedded via
//go:embed - Error handling: Services return typed errors, handlers convert to RFC 8628 OAuth responses
OAuth 2.0 Flows
POST /oauth/device/code- Request device code (Device Flow)GET /oauth/authorize- Authorization consent page (Auth Code Flow)POST /oauth/authorize- Submit consent decision (Auth Code Flow)POST /oauth/token- Token exchange endpointgrant_type=urn:ietf:params:oauth:grant-type:device_code- Exchange device_code for tokensgrant_type=authorization_code- Exchange authorization code for tokensgrant_type=refresh_token- Refresh access token
GET /oauth/tokeninfo- Verify JWT validityPOST /oauth/revoke- Revoke tokens (RFC 7009)
User Device & App Management
GET /device- Device code entry page (protected, requires login)POST /device/verify- Submit device code to authorize (protected)GET /account/sessions- Manage active token sessionsGET /account/authorizations- Manage per-app consent grantsPOST /account/authorizations/:uuid/revoke- Revoke specific app authorization
Authentication
GET /login- Login pagePOST /login- Submit credentialsGET /logout- Clear sessionGET /oauth/:provider- Initiate OAuth login (GitHub, Gitea, Microsoft)GET /oauth/:provider/callback- OAuth callback handler
Admin
GET /admin/clients- List OAuth applicationsGET /admin/clients/new- Create new OAuth clientPOST /admin/clients- Save new OAuth clientGET /admin/clients/:id/edit- Edit OAuth clientPOST /admin/clients/:id/update- Update OAuth clientGET /admin/clients/:id/authorizations- View all authorized users for a clientPOST /admin/clients/:id/revoke-all- Force re-auth for all users of a clientGET /admin/users- List usersGET /admin/users/new- Create user form (admin-created users start active, local auth)POST /admin/users- Create user (auto-generates a random password if none supplied)GET /admin/users/:id- View user detail (active tokens, OAuth connections, authorized apps)GET /admin/users/:id/edit- Edit user formPOST /admin/users/:id- Update userPOST /admin/users/:id/reset-password- Generate a new random password (local users only)POST /admin/users/:id/delete- Delete userPOST /admin/users/:id/disable- Disable account (revokes all tokens, blocks future logins)POST /admin/users/:id/enable- Re-enable a disabled accountGET /admin/users/:id/connections- List the user's third-party OAuth connectionsPOST /admin/users/:id/connections/:conn_id/delete- Unlink a specific OAuth connectionGET /admin/users/:id/authorizations- List apps the user has authorizedPOST /admin/users/:id/authorizations/:uuid/revoke- Revoke a specific user authorizationGET /admin/audit- View audit logs with filtering (admin only)GET /admin/audit/export- Export audit logs as CSV (admin only)
System
GET /.well-known/openid-configuration- OIDC Discovery metadataGET /.well-known/jwks.json- JWKS public keys (for RS256/ES256)GET /health- Health check with database connection testGET /metrics- Prometheus metrics (optional Bearer token auth)GET /api/swagger/*- Swagger/OpenAPI documentation
Key configuration categories (see .env.example and docs/CONFIGURATION.md for complete details):
Core Settings
SERVER_ADDR,BASE_URL- Server address and public URLJWT_SECRET,SESSION_SECRET- Must be changed in production (useopenssl rand -hex 32)JWT_SIGNING_ALGORITHM- HS256 (default), RS256, or ES256; asymmetric keys requireJWT_PRIVATE_KEY_PATHJWT_EXPIRATION- Access token lifetime (default: 1h);JWT_EXPIRATION_JITTER- Random expiry offset to prevent thundering herdDATABASE_DRIVER(sqlite/postgres),DATABASE_DSN- Database configuration
Authentication & Authorization
AUTH_MODE(local/http_api) - Authentication backendENABLE_REFRESH_TOKENS,ENABLE_TOKEN_ROTATION- Refresh token modes (fixed vs rotation)
Security Features
ENABLE_RATE_LIMIT,RATE_LIMIT_STORE(memory/redis) - Rate limitingENABLE_AUDIT_LOGGING- Comprehensive audit trailsSESSION_FINGERPRINT- Session security (User-Agent validation)CORS_ENABLED,CORS_ALLOWED_ORIGINS- CORS for SPA frontends (applied to/oauth/*only)
OAuth Providers
GITHUB_OAUTH_ENABLED,GITEA_OAUTH_ENABLED,MICROSOFT_OAUTH_ENABLED- Third-party OAuth login
Token Cache
TOKEN_CACHE_ENABLED- Enable token verification cache (default: false)TOKEN_CACHE_TYPE(memory/redis/redis-aside) - Cache backend for token verificationTOKEN_CACHE_TTL- Cache lifetime (default: 10h, matchesJWT_EXPIRATION); revocation uses explicit invalidationTOKEN_CACHE_CLIENT_TTL- Redis-aside client-side TTL (default: 1h); RESP3 handles real-time invalidation
Observability
METRICS_ENABLED- Prometheus metrics endpointMETRICS_CACHE_TYPE(memory/redis/redis-aside) - Metrics caching for multi-instance deploymentsMETRICS_GAUGE_UPDATE_ENABLED- Set to false on all but one replica in multi-instance setups
Seeded automatically on first run (store/sqlite.go:seedData):
- User:
admin/<random_password>(16-character random password, written toauthgate-credentials.txton first run, bcrypt hashed) - Client:
AuthGate CLI(client_id is auto-generated UUID, written toauthgate-credentials.txton first run)
github.com/go-authgate/device-cli contains a demo CLI that demonstrates the device flow:
git clone https://github.com/go-authgate/device-cli
cd device-cli
cp .env.example .env # Add CLIENT_ID from authgate-credentials.txt
go run main.gogithub.com/go-authgate/oauth-cli demonstrates Authorization Code Flow + PKCE.
For a hybrid CLI that auto-detects the environment (browser on local, Device Code over SSH), see github.com/go-authgate/cli.
When AUTH_MODE=http_api, AuthGate delegates authentication to external API:
- Request: POST to
HTTP_API_URLwith{"username": "...", "password": "..."} - Response:
{"success": true, "user_id": "...", "email": "...", "full_name": "..."} - First login auto-creates user with
auth_source="http_api" - Default admin user always uses local auth (failsafe)
Secure communication with external APIs using HTTP_API_AUTH_MODE:
none- No authentication (default, trusted networks only)simple- Shared secret header (e.g.,X-API-Secret: your-secret)hmac- HMAC-SHA256 signature with timestamp validation (production recommended)
- Use
http.StatusOK,http.StatusBadRequest, etc. instead of numeric status codes - Services return typed errors, handlers convert to appropriate HTTP responses
- GORM models use
gorm.Modelfor CreatedAt/UpdatedAt/DeletedAt (except custom timestamp models) - Handlers accept both form-encoded and JSON request bodies where applicable
- All static assets and templates are embedded via
//go:embedfor single-binary deployment - Templates: Use templ for type-safe HTML templates
.templfiles are compiled to_templ.gofiles viamake generate- Never edit
_templ.gofiles directly - always edit the source.templfiles - Run
make generateafter modifying any.templfile
- Interfaces: Follow Go best practices - define interfaces where abstraction adds value
- Currently have interfaces:
Cache,MetricsCollector(swappable implementations) - Should consider adding interfaces for pluggable components to improve extensibility and testability:
AuthProvider- Already has 3 implementations (local, http_api, OAuth); interface would enable third-party auth backendsTokenProvider- Currently used for mock-based testing; interface would enable custom token formatsStore- Supports SQLite/PostgreSQL; interface would enable MySQL, MongoDB, or custom storage backends
- Don't need interfaces: Services (UserService, DeviceService, etc.) - these are core business logic with single implementations
- Use direct struct dependency injection for simplicity where multiple implementations are unlikely
- Currently have interfaces:
- Audit Logging: Services that modify data should log audit events
- Use
auditService.Log()for normal events (async, non-blocking) - Use
auditService.LogSync()for critical security events (synchronous) - Sensitive data is automatically masked by AuditService (passwords, tokens, secrets)
- Use
- API Documentation: Use Swagger annotations in handlers for OpenAPI generation
- Annotations use swaggo format:
// @Summary,// @Description,// @Tags, etc. - Run
make swaggerto regenerate documentation
- Annotations use swaggo format:
- IMPORTANT: Before committing changes:
- Generate code: Run
make generateto compile templates and update Swagger docs - Write tests: All new features and bug fixes MUST include corresponding unit tests
- Format code: Run
make fmtto automatically fix formatting issues - Pass linting: Run
make lintto verify code passes linting without errors
- Generate code: Run