# Authentication Authentication providers supporting OIDC and username/password authentication with JWT-based sessions. ## Overview The auth package implements authentication middleware and login handlers that integrate with GoDoxy's HTTP routing system. It provides flexible authentication that can be enabled/disabled based on configuration and supports multiple authentication providers. ### Primary consumers - `internal/route/rules` - Authentication middleware for routes - `internal/api/v1/auth` - Login and session management endpoints - `internal/homepage` - WebUI login page ### Non-goals - ACL or authorization (see `internal/acl`) - User management database - Multi-factor authentication - Rate limiting (basic OIDC rate limiting only) ### Stability Stable internal package. Public API consists of the `Provider` interface and initialization functions. ## Public API ### Exported types ```go type Provider interface { CheckToken(r *http.Request) error LoginHandler(w http.ResponseWriter, r *http.Request) PostAuthCallbackHandler(w http.ResponseWriter, r *http.Request) LogoutHandler(w http.ResponseWriter, r *http.Request) } ``` ### OIDC Provider ```go type OIDCProvider struct { oauthConfig *oauth2.Config oidcProvider *oidc.Provider oidcVerifier *oidc.IDTokenVerifier endSessionURL *url.URL allowedUsers []string allowedGroups []string rateLimit *rate.Limiter } ``` ### Username/Password Provider ```go type UserPassAuth struct { username string pwdHash []byte secret []byte tokenTTL time.Duration } ``` ### Exported functions ```go func Initialize() error ``` Sets up authentication providers based on environment configuration. Returns error if OIDC issuer is configured but cannot be reached. ```go func IsEnabled() bool ``` Returns whether authentication is enabled. Checks `DEBUG_DISABLE_AUTH`, `API_JWT_SECRET`, and `OIDC_ISSUER_URL`. ```go func IsOIDCEnabled() bool ``` Returns whether OIDC authentication is configured. ```go func GetDefaultAuth() Provider ``` Returns the configured authentication provider. ```go func AuthCheckHandler(w http.ResponseWriter, r *http.Request) ``` HTTP handler that checks if the request has a valid token. Returns 200 if valid, invokes login handler otherwise. ```go func AuthOrProceed(w http.ResponseWriter, r *http.Request) bool ``` Authenticates request or proceeds if valid. Returns `false` if login handler was invoked, `true` if authenticated. ```go func ProceedNext(w http.ResponseWriter, r *http.Request) ``` Continues to the next handler after successful authentication. ```go func NewUserPassAuth(username, password string, secret []byte, tokenTTL time.Duration) (*UserPassAuth, error) ``` Creates a new username/password auth provider with bcrypt password hashing. ```go func NewUserPassAuthFromEnv() (*UserPassAuth, error) ``` Creates username/password auth from environment variables `API_USER`, `API_PASSWORD`, `API_JWT_SECRET`. ```go func NewOIDCProvider(issuerURL, clientID, clientSecret string, allowedUsers, allowedGroups []string) (*OIDCProvider, error) ``` Creates a new OIDC provider. Returns error if issuer cannot be reached or no allowed users/groups are configured. ```go func NewOIDCProviderFromEnv() (*OIDCProvider, error) ``` Creates OIDC provider from environment variables `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, etc. ## Architecture ### Core components ```mermaid graph TD A[HTTP Request] --> B{Auth Enabled?} B -->|No| C[Proceed Direct] B -->|Yes| D[Check Token] D -->|Valid| E[Proceed] D -->|Invalid| F[Login Handler] G[OIDC Provider] --> H[Token Validation] I[UserPass Provider] --> J[Credential Check] F --> K{OIDC Configured?} K -->|Yes| G K -->|No| I subgraph Cookie Management L[Token Cookie] M[State Cookie] N[Session Cookie] end ``` ### OIDC authentication flow ```mermaid sequenceDiagram participant User participant App participant IdP User->>App: Access Protected Resource App->>App: Check Token alt No valid token App-->>User: Redirect to /auth/ User->>IdP: Login & Authorize IdP-->>User: Redirect with Code User->>App: /auth/callback?code=... App->>IdP: Exchange Code for Token IdP-->>App: Access Token + ID Token App->>App: Validate Token App->>App: Check allowed users/groups App-->>User: Protected Resource else Valid token exists App-->>User: Protected Resource end ``` ### Username/password flow ```mermaid sequenceDiagram participant User participant App User->>App: POST /auth/callback App->>App: Validate credentials alt Valid App->>App: Generate JWT App-->>User: Set token cookie, redirect to / else Invalid App-->>User: 401 Unauthorized end ``` ## Configuration Surface ### Environment variables | Variable | Description | | ------------------------ | ----------------------------------------------------------- | | `DEBUG_DISABLE_AUTH` | Set to "true" to disable auth for debugging | | `API_JWT_SECRET` | Secret key for JWT token validation (enables userpass auth) | | `API_USER` | Username for userpass authentication | | `API_PASSWORD` | Password for userpass authentication | | `API_JWT_TOKEN_TTL` | Token TTL duration (default: 24h) | | `OIDC_ISSUER_URL` | OIDC provider URL (enables OIDC) | | `OIDC_CLIENT_ID` | OIDC client ID | | `OIDC_CLIENT_SECRET` | OIDC client secret | | `OIDC_REDIRECT_URL` | OIDC redirect URL | | `OIDC_ALLOWED_USERS` | Comma-separated list of allowed users | | `OIDC_ALLOWED_GROUPS` | Comma-separated list of allowed groups | | `OIDC_SCOPES` | Comma-separated OIDC scopes (default: openid,profile,email) | | `OIDC_RATE_LIMIT` | Rate limit requests (default: 10) | | `OIDC_RATE_LIMIT_PERIOD` | Rate limit period (default: 1m) | ### Hot-reloading Authentication configuration requires restart. No dynamic reconfiguration is supported. ## Dependency and Integration Map ### Internal dependencies - `internal/common` - Environment variable access ### External dependencies - `golang.org/x/crypto/bcrypt` - Password hashing - `github.com/coreos/go-oidc/v3/oidc` - OIDC protocol - `golang.org/x/oauth2` - OAuth2/OIDC implementation - `github.com/golang-jwt/jwt/v5` - JWT token handling - `golang.org/x/time/rate` - OIDC rate limiting ### Integration points ```go // Route middleware uses AuthOrProceed routeHandler := func(w http.ResponseWriter, r *http.Request) { if !auth.AuthOrProceed(w, r) { return // Auth failed, login handler was invoked } // Continue with authenticated request } ``` ## Observability ### Logs - OIDC provider initialization errors - Token validation failures - Rate limit exceeded events ### Metrics No metrics are currently exposed. ## Security Considerations - JWT tokens use HS512 signing for userpass auth - OIDC tokens are validated against the issuer - Session tokens are scoped by client ID to prevent conflicts - Passwords are hashed with bcrypt (cost 10) - OIDC rate limiting prevents brute-force attacks - State parameter prevents CSRF attacks - Refresh tokens are stored and invalidated on logout ## Failure Modes and Recovery | Failure | Behavior | Recovery | | ------------------------ | ------------------------------ | ----------------------------- | | OIDC issuer unreachable | Initialize returns error | Fix network/URL configuration | | Invalid JWT secret | Initialize uses API_JWT_SECRET | Provide correct secret | | Token expired | CheckToken returns error | User must re-authenticate | | User not in allowed list | Returns ErrUserNotAllowed | Add user to allowed list | | Rate limit exceeded | Returns 429 Too Many Requests | Wait for rate limit reset | ## Usage Examples ### Basic setup ```go // Initialize authentication during startup err := auth.Initialize() if err != nil { log.Fatal(err) } // Check if auth is enabled if auth.IsEnabled() { log.Println("Authentication is enabled") } // Check OIDC status if auth.IsOIDCEnabled() { log.Println("OIDC authentication configured") } ``` ### Using AuthOrProceed middleware ```go func protectedHandler(w http.ResponseWriter, r *http.Request) { if !auth.AuthOrProceed(w, r) { return // Auth failed, login handler was invoked } // Continue with authenticated request } ``` ### Using AuthCheckHandler ```go http.HandleFunc("/api/", auth.AuthCheckHandler(apiHandler)) ``` ### Custom OIDC provider ```go provider, err := auth.NewOIDCProvider( "https://your-idp.com", "your-client-id", "your-client-secret", []string{"user1", "user2"}, []string{"group1"}, ) if err != nil { log.Fatal(err) } ``` ### Custom userpass provider ```go provider, err := auth.NewUserPassAuth( "admin", "password123", []byte("jwt-secret-key"), 24*time.Hour, ) if err != nil { log.Fatal(err) } ```