Files
cronmaster/howto/SSO.md
2025-11-11 11:33:51 +00:00

253 lines
8.8 KiB
Markdown

# SSO with OIDC
Cr*nMaster supports any OIDC provider (Authentik, Auth0, Keycloak, Okta, etc.) with these requirements:
- Supports PKCE (most modern providers do)
- Can be configured as a public client (no client secret needed)
- Provides standard OIDC scopes (openid, profile, email)
## Configuration
### 1. Configure your OIDC Provider
- Client Type: Public
- Grant Type: Authorization Code with PKCE
- Scopes: openid, profile, email
- Redirect URI: `https://YOUR_APP_HOST/api/oidc/callback`
- Post-logout URI: `https://YOUR_APP_HOST/login`
### 2. Get these values from your provider
- Client ID
- OIDC Issuer URL (usually ends with `.well-known/openid-configuration`)
### 3. Set environment variables
```yaml
services:
cronmaster:
environment:
- SSO_MODE=oidc
- OIDC_ISSUER=https://YOUR_SSO_HOST/issuer/path
- OIDC_CLIENT_ID=your_client_id
- APP_URL=https://your-cronmaster-domain.com # Required for OIDC, defaults to http://localhost:<port>
# Optional:
- OIDC_CLIENT_SECRET=your_client_secret # For confidential client mode (uses client secret instead of PKCE)
- OIDC_LOGOUT_URL=https://provider.com/logout # Custom logout URL (bypasses discovery)
- OIDC_GROUPS_SCOPE=groups # Scope for groups claim, set to "no" or "false" to disable
- INTERNAL_API_URL=http://localhost:3000 # Use if getting 403 errors after login (reverse proxy issues)
- DEBUGGER=true # Enable detailed OIDC flow logging
- HTTPS=true # Set if running in production with HTTPS (affects secure cookie flag)
```
**Note**: When `OIDC_CLIENT_SECRET` is set, Cr*nMaster switches to confidential client mode using client authentication instead of PKCE.
### Combining Password and SSO Authentication
You can enable both password-based authentication (`AUTH_PASSWORD`) and OIDC at the same time:
```yaml
environment:
- AUTH_PASSWORD=your_password
- SSO_MODE=oidc
- OIDC_ISSUER=https://your-sso-provider.com
- OIDC_CLIENT_ID=your_client_id
- APP_URL=https://your-cronmaster-domain.com
```
When both are enabled, the login page will display:
- Password input field
- SSO button ("Sign in with SSO")
Users can choose either method to authenticate.
## Environment Variables Reference
### Required for OIDC
| Variable | Description |
|----------|-------------|
| `SSO_MODE` | Set to `oidc` to enable SSO authentication |
| `OIDC_ISSUER` | OIDC provider issuer URL (e.g., `https://provider.com` or `https://provider.com/realm`) |
| `OIDC_CLIENT_ID` | Client ID from your OIDC provider |
| `APP_URL` | Public URL of your Cronmaster instance (e.g., `https://cron.domain.com`) |
### Optional
| Variable | Default | Description |
|----------|---------|-------------|
| `OIDC_CLIENT_SECRET` | None | Client secret for confidential client mode |
| `OIDC_LOGOUT_URL` | None | Custom logout URL (skips OIDC discovery for logout) |
| `OIDC_GROUPS_SCOPE` | `groups` | Scope to request groups claim. Set to `no` or `false` to disable |
| `INTERNAL_API_URL` | `APP_URL` | Internal URL for API calls (use if behind reverse proxy with 403 errors) |
| `HTTPS` | `false` | Set to `true` in production with HTTPS (enables `__Host-` cookie prefix and secure flag) |
| `DEBUGGER` | `false` | Enable detailed logging for OIDC flow debugging |
| `NODE_TLS_REJECT_UNAUTHORIZED` | `1` | Set to `0` to allow self-signed certificates (DEV ONLY - UNSAFE in production!) |
## Verified Providers
These providers have been tested:
- **Auth0** - `OIDC_ISSUER=https://YOUR_TENANT.REGION.auth0.com`
- **Authentik** - `OIDC_ISSUER=https://YOUR_DOMAIN/application/o/APP_SLUG/`
- **Keycloak**
- **Okta**
Other standard OIDC providers should work as well.
## How It Works
1. User clicks "Sign in with SSO" button
2. User redirected to OIDC provider's authorization endpoint with:
- `response_type=code`
- PKCE challenge (or client secret if configured)
- `state` and `nonce` for security
- Requested scopes: `openid profile email` (and groups if enabled)
3. User authenticates with the provider
4. Provider redirects back to `/api/oidc/callback` with authorization code
5. Cr*nMaster exchanges code for ID token using:
- PKCE verifier (or client secret)
- Validates `state` matches
6. ID token verified using provider's JWKS (public keys):
- Issuer validation
- Audience validation (client ID)
- Nonce validation
- Signature verification
7. Secure session created with:
- Cryptographically random session ID (32 bytes, base64url)
- Stored in `data/sessions/sessions.json`
- 30-day expiration
8. Session cookie set:
- Name: `__Host-cronmaster-session` (production with HTTPS) or `cronmaster-session`
- HttpOnly, Secure (if HTTPS), SameSite=Lax
9. Session validated on each request via middleware → `/api/auth/check-session`
## Troubleshooting
### 403 Forbidden After SSO Login (Reverse Proxy)
If you successfully authenticate via SSO but get redirected back to login, and your logs show:
```
MIDDLEWARE - Session Check Response:
status: 403
MIDDLEWARE - session is not ok
```
**Solution**: Set `INTERNAL_API_URL`:
```yaml
environment:
- INTERNAL_API_URL=http://localhost:3000
```
This tells the middleware to use localhost for session validation instead of going through the reverse proxy.
**Why**: When `APP_URL` is set to your external domain, the middleware tries to validate sessions by calling `https://external-domain.com/api/auth/check-session`, which goes through your reverse proxy and may get blocked with 403.
### Login Redirect Loop
If stuck in a redirect loop:
1. Verify `APP_URL` matches your public URL exactly
2. Check redirect URI in provider matches: `https://YOUR_DOMAIN/api/oidc/callback`
3. Enable `DEBUGGER=true` to see detailed logs
4. Check browser console for errors
5. Verify OIDC provider discovery URL is accessible: `{OIDC_ISSUER}/.well-known/openid-configuration`
### Self-Signed Certificate Error
If you see this error in logs:
```
[OIDC Login] Error: TypeError: fetch failed
cause: Error: self-signed certificate
code: 'DEPTH_ZERO_SELF_SIGNED_CERT'
```
**Solution for development**: Set `NODE_TLS_REJECT_UNAUTHORIZED=0`:
```yaml
environment:
- NODE_TLS_REJECT_UNAUTHORIZED=0
```
**⚠️ WARNING**: This disables SSL certificate validation and should ONLY be used in development environments with self-signed certificates. NEVER use this in production!
**Production solution**: Use proper SSL certificates from a trusted CA (Let's Encrypt, etc.)
### 401 Unauthorized After Login
If you authenticate but immediately see login page again:
1. Check cookies are being set (browser dev tools → Application → Cookies)
2. Look for `cronmaster-session` or `__Host-cronmaster-session` cookie
3. Enable `DEBUGGER=true` to see session validation in middleware
4. Check `data/sessions/sessions.json` file exists and has your session
5. Verify file permissions on `data/` directory
### Provider-Specific Errors
**"Invalid client"**
- Client ID or secret is incorrect
- Verify values in provider configuration
**"Invalid redirect URI"**
- Redirect URI doesn't match exactly
- Must include protocol: `https://` not just `domain.com`
- Must match `APP_URL` + `/api/oidc/callback`
- Case-sensitive
**"Invalid scope"**
- Provider doesn't support requested scope
- Try setting `OIDC_GROUPS_SCOPE=no` to disable groups scope
- Some providers (like Google) don't support `groups` scope
**"Nonce mismatch"**
- Browser cookies being blocked or cleared
- Check cookie settings and privacy mode
- May need to adjust SameSite cookie settings
**"JWKS fetch failed"**
- Provider's JWKS endpoint not accessible
- Check firewall/network rules
- Verify OIDC_ISSUER is correct
## Security Notes
- ✅ Sessions valid for 30 days, stored with cryptographically random IDs
- ✅ PKCE used by default (no client secret in authorization URL)
- ✅ ID tokens verified with JWKS (provider's public keys)
- ✅ State parameter prevents CSRF
- ✅ Nonce parameter prevents replay attacks
- ✅ HttpOnly cookies prevent XSS
- ✅ Secure flag on cookies in production with HTTPS
-`__Host-` cookie prefix in production for additional security
- ✅ Clock tolerance of 5 seconds for token validation
**Production Recommendations:**
- Set `HTTPS=true` when running with HTTPS
- Use `OIDC_CLIENT_SECRET` if provider supports confidential clients (more secure than PKCE)
- Set strong, random `API_KEY` to protect API endpoints
- Use `INTERNAL_API_URL` when behind reverse proxy
- Regularly rotate `OIDC_CLIENT_SECRET` if using confidential client mode
## Debugging
Enable debug mode to see detailed OIDC flow:
```yaml
environment:
- DEBUGGER=true
```
You'll see logs for:
- `[OIDC Login]` - Authorization redirect
- `[OIDC Callback]` - Token exchange and JWT verification
- `[OIDC Logout]` - Logout flow
- `[Session]` - Session creation/validation/deletion
- `MIDDLEWARE` - Session check and URL resolution
Check both server logs and browser console for complete picture.