Files

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

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

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

type UserPassAuth struct {
    username string
    pwdHash  []byte
    secret   []byte
    tokenTTL time.Duration
}

Exported functions

func Initialize() error

Sets up authentication providers based on environment configuration. Returns error if OIDC issuer is configured but cannot be reached.

func IsEnabled() bool

Returns whether authentication is enabled. Checks DEBUG_DISABLE_AUTH, API_JWT_SECRET, and OIDC_ISSUER_URL.

func IsOIDCEnabled() bool

Returns whether OIDC authentication is configured.

func GetDefaultAuth() Provider

Returns the configured authentication provider.

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.

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.

func ProceedNext(w http.ResponseWriter, r *http.Request)

Continues to the next handler after successful authentication.

func NewUserPassAuth(username, password string, secret []byte, tokenTTL time.Duration) (*UserPassAuth, error)

Creates a new username/password auth provider with bcrypt password hashing.

func NewUserPassAuthFromEnv() (*UserPassAuth, error)

Creates username/password auth from environment variables API_USER, API_PASSWORD, API_JWT_SECRET.

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.

func NewOIDCProviderFromEnv() (*OIDCProvider, error)

Creates OIDC provider from environment variables OIDC_ISSUER_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, etc.

Architecture

Core components

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

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

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

// 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

// 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

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

http.HandleFunc("/api/", auth.AuthCheckHandler(apiHandler))

Custom OIDC provider

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

provider, err := auth.NewUserPassAuth(
    "admin",
    "password123",
    []byte("jwt-secret-key"),
    24*time.Hour,
)
if err != nil {
    log.Fatal(err)
}