Security improvements:
1. Client secret hashing:
- Replace plaintext comparison with bcrypt.CompareHashAndPassword
- Provides constant-time comparison to prevent timing attacks
- Hash secrets with bcrypt before storing in database
- Update SyncClientsFromConfig to hash incoming plaintext secrets
2. Deterministic RSA key loading:
- Load most recently created key using ORDER BY created_at DESC
- Add warning if multiple keys detected in database
- Ensures consistent key selection on startup
3. Optional RSA key encryption:
- Encrypt private keys with AES-256-GCM when OIDC_RSA_MASTER_KEY is set
- Master key derived via SHA256 from environment variable
- Backward compatible: stores plaintext if no master key set
- Automatic detection of encrypted vs plaintext on load
All changes maintain backward compatibility with existing deployments.
Authorization codes were implemented as stateless JWTs with no tracking,
allowing the same code to be exchanged for tokens multiple times. This
violates OAuth 2.0 RFC 6749 Section 4.1.2 which mandates that authorization
codes MUST be single-use.
This change:
- Adds oidc_authorization_codes table to track code usage
- Stores authorization codes in database when generated
- Validates code exists and hasn't been used before exchange
- Marks code as used immediately after validation
- Prevents replay attacks where intercepted codes could be reused
Security impact:
- Prevents attackers from reusing intercepted authorization codes
- Ensures compliance with OAuth 2.0 security requirements
- Adds database-backed single-use enforcement
The variable 'html' was being assigned to store HTML content, which
caused Python to treat 'html' as a local variable throughout the
function. This prevented access to the 'html' module (imported at
the top) within f-strings that referenced html.escape().
Renamed the HTML content variable to 'html_content' to avoid the
naming conflict with the html module.
PKCE was advertised in the discovery document but not actually implemented.
This commit adds full PKCE support:
- Store code_challenge and code_challenge_method in authorization code JWT
- Accept code_verifier parameter in token endpoint
- Validate code_verifier against stored code_challenge
- Support both S256 (SHA256) and plain code challenge methods
- PKCE validation is required when code_challenge is present
This prevents authorization code interception attacks by requiring
the client to prove possession of the code_verifier that was used
to generate the code_challenge.
The validateAccessToken method was only decoding the JWT payload without
verifying the signature, allowing attackers to forge tokens. This fix:
- Adds ValidateAccessToken method to OIDCService that properly verifies
JWT signature using RSA public key
- Validates issuer, expiration, and required claims
- Updates controller to use the secure validation method
- Removes insecure manual JWT parsing code
This commit adds OpenID Connect (OIDC) provider functionality to tinyauth,
allowing it to act as an OIDC identity provider for other applications.
Features:
- OIDC discovery endpoint at /.well-known/openid-configuration
- Authorization endpoint for OAuth 2.0 authorization code flow
- Token endpoint for exchanging authorization codes for tokens
- ID token generation with JWT signing
- JWKS endpoint for public key distribution
- Support for PKCE (code challenge/verifier)
- Nonce validation for ID tokens
- Configurable OIDC clients with redirect URIs, scopes, and grant types
Validation:
- Docker Compose setup for local testing
- OIDC test client (oidc-whoami) with session management
- Nginx reverse proxy configuration
- DNS server (dnsmasq) for custom domain resolution
- Chrome launch script for easy testing
Configuration:
- OIDC configuration in config.yaml
- Example configuration in config.example.yaml
- Database migrations for OIDC client storage