Jump to heading OAuth Authentication with OIDC
Scotty provides built-in OAuth authentication with OIDC (OpenID Connect) integration. This setup offers secure authentication that protects your Scotty API endpoints while providing a seamless user experience through the web interface.
Jump to heading Overview
Scotty supports three authentication modes configured via auth_mode:
dev: Development mode with no authentication (uses fixed dev user)oauth: Native OAuth authentication with OIDC provider integrationbearer: Traditional token-based authentication
In OAuth mode, Scotty handles the complete OAuth 2.0 Authorization Code flow with PKCE (Proof Key for Code Exchange) for enhanced security.
Jump to heading How OAuth Mode Works
Jump to heading Architecture
User → Frontend SPA → Scotty OAuth Endpoints → OIDC Provider
↓
Session Management
↓
User Authentication
Jump to heading Authentication Flow
- User initiates login via the Scotty frontend
- Frontend redirects to Scotty's
/oauth/authorizeendpoint - Scotty generates authorization URL with PKCE challenge and redirects to OIDC provider
- User authenticates with OIDC provider
- OIDC provider redirects back to Scotty's
/api/oauth/callbackendpoint with authorization code - Scotty exchanges authorization code for access token using PKCE verifier
- User information is extracted via OIDC
/oauth/userinfoendpoint and session is created - Frontend exchanges session for tokens and stores OAuth tokens and user info in localStorage
Jump to heading Route Protection
- Public:
/,/api/v1/health,/api/v1/info,/api/v1/login,/oauth/*, static assets, SPA routes - Protected:
/api/v1/authenticated/*- all API operations that modify state
Jump to heading Setup Instructions
Jump to heading 1. OIDC Provider OAuth Application
Configure your OIDC provider (GitLab, Auth0, Keycloak, etc.):
Jump to heading GitLab Example:
- Go to GitLab → Settings → Applications
- Create new application:
- Name: Scotty
- Redirect URI:
http://localhost:21342/api/oauth/callback - Scopes:
openid,profile,email,read_user
- Save the Application ID and Secret
Jump to heading Other OIDC Providers:
- Auth0: Create application in Auth0 dashboard
- Keycloak: Create client in Keycloak admin console
- Google: Use Google Cloud Console OAuth 2.0 setup
Jump to heading 2. Scotty Configuration
Configure Scotty for OAuth mode in config/local.yaml:
api:
bind_address: "0.0.0.0:21342"
auth_mode: "oauth"
oauth:
oidc_issuer_url: "https://gitlab.com" # or your OIDC provider URL
client_id: "your_oidc_application_id"
client_secret: "your_oidc_application_secret"
redirect_url: "http://localhost:21342/api/oauth/callback"
frontend_base_url: "http://localhost:21342" # Base URL for frontend redirects (default: http://localhost:21342)
Provider-specific examples:
# GitLab
oauth:
oidc_issuer_url: "https://gitlab.com"
# Auth0
oauth:
oidc_issuer_url: "https://your-domain.auth0.com"
# Keycloak
oauth:
oidc_issuer_url: "https://your-keycloak.com/auth/realms/your-realm"
# Google
oauth:
oidc_issuer_url: "https://accounts.google.com"
Jump to heading 3. Environment Variables
Alternatively, you can use environment variables:
# Set authentication mode
SCOTTY__API__AUTH_MODE=oauth
# OIDC OAuth Application credentials
SCOTTY__API__OAUTH__CLIENT_ID=your_oidc_application_id
SCOTTY__API__OAUTH__CLIENT_SECRET=your_oidc_application_secret
# OAuth configuration
SCOTTY__API__OAUTH__OIDC_ISSUER_URL=https://gitlab.com
SCOTTY__API__OAUTH__REDIRECT_URL=http://localhost:21342/api/oauth/callback
SCOTTY__API__OAUTH__FRONTEND_BASE_URL=http://localhost:21342
Jump to heading Understanding OAuth URLs
Scotty uses two different URL configurations for OAuth:
Jump to heading
redirect_url - Backend OAuth Callback
- Purpose: The OAuth callback endpoint where the OIDC provider redirects after user authentication
- Used by: OIDC provider (GitLab, Auth0, etc.)
- Must match: The redirect URI configured in your OIDC provider's OAuth application settings
- Example:
http://localhost:21342/api/oauth/callback - Format: Full URL to Scotty's backend
/api/oauth/callbackendpoint
Jump to heading
frontend_base_url - Frontend Application Base URL
- Purpose: The base URL of your frontend application where users are redirected after OAuth completes
- Used by: Scotty backend to redirect users back to the frontend with session ID
- Must match: The actual URL where your users access Scotty's web interface
- Example:
http://localhost:21342(development) orhttps://scotty.example.com(production) - Format: Base URL only (no path) - Scotty appends
/oauth/callback?session_id=xyz
Production Example:
oauth:
redirect_url: "https://scotty.example.com/api/oauth/callback"
frontend_base_url: "https://scotty.example.com"
Important: Both URLs must match your production domain. Using localhost in production will break the OAuth flow.
Jump to heading OAuth Endpoints
Scotty provides the following OAuth endpoints:
Jump to heading
GET /oauth/authorize
Initiates the OAuth authorization flow. Redirects to OIDC provider with proper PKCE parameters.
Query Parameters:
redirect_uri(optional): Where to redirect after successful authentication
Jump to heading
GET /api/oauth/callback
Handles the OAuth callback from OIDC provider. Exchanges authorization code for access token and creates temporary session.
Query Parameters:
code: Authorization code from OIDC providerstate: CSRF protection token with embedded session ID
Jump to heading
POST /oauth/exchange
Exchanges temporary session for OAuth tokens (used by frontend).
Request Body:
session_id: Temporary session identifier
Jump to heading User Information
After successful OAuth authentication, Scotty provides OIDC-standard user information:
- Subject (sub): OIDC user ID (typically a string)
- Username: Preferred username (optional)
- Name: User's display name (optional)
- Email: User's email address (optional)
- Access Token: OAuth access token for API calls
This information is available to both the frontend (stored in sessionStorage) and backend (through authentication middleware).
Jump to heading Development vs Production
Jump to heading Development Setup
For local development, start Scotty with OAuth configuration:
# Set OAuth configuration
export SCOTTY__API__AUTH_MODE=oauth
export SCOTTY__API__OAUTH__CLIENT_ID=your_client_id
export SCOTTY__API__OAUTH__CLIENT_SECRET=your_client_secret
# Run Scotty
cargo run --bin scotty
Jump to heading Development Mode Alternative
For faster iteration during development, you can use auth_mode: "dev":
api:
auth_mode: "dev"
dev_user_email: "developer@localhost"
dev_user_name: "Local Developer"
This bypasses OAuth and uses a fixed development user.
Jump to heading Production Considerations
- Use HTTPS: Configure TLS for your domain and update redirect URLs
- Proper domains: Replace
localhostwith your actual domain in all configurations - Secure secrets: Use environment variables or secret management systems
- CORS configuration: Ensure proper CORS settings for your domain
- Session security: Configure appropriate session timeouts
Jump to heading Hybrid Authentication: OAuth + Bearer Tokens
Use Case: OAuth for human users + bearer tokens for service accounts (CI/CD, monitoring, automation).
Jump to heading Overview
When auth_mode is set to oauth, Scotty supports an optional bearer token fallback for service accounts. This hybrid approach provides:
- OAuth authentication for human users (web UI, CLI via device flow)
- Bearer token authentication for service accounts (CI/CD pipelines, monitoring tools, automation scripts)
- Single authentication mode - no need to switch between modes
Jump to heading How Hybrid Authentication Works
Authentication Flow (Optimized for Performance):
- Extract token from
Authorization: Bearer <token>header - Check bearer tokens FIRST (fast HashMap lookup) → If found, authenticate as service account
- Try OAuth validation (network call to OIDC provider) → If bearer check fails
- Return authentication result or 401 Unauthorized
This order ensures service accounts experience zero OAuth latency while still supporting OAuth for human users.
Jump to heading Configuration
Enable hybrid authentication by configuring both OAuth and bearer tokens:
api:
auth_mode: oauth # Enable OAuth mode
# OAuth configuration for human users
oauth:
oidc_issuer_url: "https://gitlab.com"
client_id: "your_oidc_application_id"
client_secret: "your_oidc_application_secret"
redirect_url: "http://localhost:21342/api/oauth/callback"
frontend_base_url: "http://localhost:21342"
# Bearer tokens for service accounts (checked first for performance)
bearer_tokens:
ci-bot: "OVERRIDE_VIA_ENV_VAR" # CI/CD pipeline
monitoring: "OVERRIDE_VIA_ENV_VAR" # Monitoring service
deployment: "OVERRIDE_VIA_ENV_VAR" # Deployment automation
Environment Variable Configuration:
# Set OAuth credentials
export SCOTTY__API__OAUTH__CLIENT_ID=your_client_id
export SCOTTY__API__OAUTH__CLIENT_SECRET=your_client_secret
# Set bearer tokens for service accounts (IMPORTANT: Use secure random tokens!)
# Note: Hyphens in config keys (ci-bot) become underscores in env vars (CI_BOT)
export SCOTTY__API__BEARER_TOKENS__CI_BOT=$(openssl rand -base64 32)
export SCOTTY__API__BEARER_TOKENS__MONITORING=$(openssl rand -base64 32)
export SCOTTY__API__BEARER_TOKENS__DEPLOYMENT=$(openssl rand -base64 32)
# Start Scotty
cargo run --bin scotty
Jump to heading RBAC Configuration for Service Accounts
Configure authorization for bearer token identifiers in config/casbin/policy.yaml:
# Scopes and roles definitions
scopes:
production:
description: Production environment
staging:
description: Staging environment
roles:
deployer:
permissions: [view, manage, create]
monitor:
permissions: [view, logs]
# Authorization assignments
assignments:
# OAuth users (human users)
"alice@example.com":
- role: admin
scopes: ["*"]
# Bearer token service accounts
# Format: identifier:<token_name> (matches bearer_tokens key)
"identifier:ci-bot":
- role: deployer
scopes: [staging, production]
"identifier:monitoring":
- role: monitor
scopes: ["*"]
"identifier:deployment":
- role: deployer
scopes: [production]
Key Concept: The identifier:<name> in policy.yaml maps to the key in bearer_tokens configuration, NOT the actual token value.
Example mapping:
- Configuration:
bearer_tokens.ci-bot: "secret-token-abc123" - Policy:
identifier:ci-bot: [role: deployer, ...] - API call:
Authorization: Bearer secret-token-abc123 - Scotty maps: token → identifier "ci-bot" → role "deployer"
Jump to heading Usage Examples
Human User (OAuth):
# Web UI: Click "Login with OAuth" button
# CLI: Use device flow
scottyctl auth:login --server https://scotty.example.com
# Use authenticated commands
scottyctl app:list
Service Account (Bearer Token):
# CI/CD Pipeline
export SCOTTY_ACCESS_TOKEN="${CI_BOT_TOKEN}" # From CI/CD secrets
scottyctl --server https://scotty.example.com app:create my-app
# Monitoring Script
curl -H "Authorization: Bearer ${MONITORING_TOKEN}" \
https://scotty.example.com/api/v1/authenticated/apps
# Deployment Automation
export SCOTTY_SERVER=https://scotty.example.com
export SCOTTY_ACCESS_TOKEN="${DEPLOYMENT_TOKEN}"
scottyctl app:restart production-app
Jump to heading Security Best Practices
-
Generate Strong Tokens for Service Accounts
# Use cryptographically secure random tokens openssl rand -base64 32 -
Never Commit Tokens to Git
- Use environment variables exclusively
- Store in CI/CD secret management
- Rotate regularly
-
Use Descriptive Identifiers
# Good: Semantic, descriptive names bearer_tokens: ci-bot: "..." monitoring-service: "..." deployment-automation: "..." # Bad: Confusing, token-like names bearer_tokens: test-bearer-token-123: "..." # Looks like a token value! -
Apply Least Privilege via RBAC
- CI/CD bot:
deployerrole (no destroy permission) - Monitoring:
monitorrole (view and logs only) - Deployment: Limited to specific scopes
- CI/CD bot:
-
Monitor and Audit
- Track which authentication method is used (logs show "Bearer token authentication successful" vs "OAuth authentication successful")
- Monitor for suspicious activity
- Rotate tokens on compromise
Jump to heading Migration from Pure OAuth
Scenario: You have an existing OAuth-only deployment and want to add service accounts.
Steps:
-
Add bearer_tokens configuration (without changing auth_mode)
api: auth_mode: oauth # Keep OAuth mode oauth: # ... existing OAuth config ... bearer_tokens: # ADD service account tokens ci-bot: "OVERRIDE_VIA_ENV_VAR" -
Configure RBAC for service accounts in
policy.yamlassignments: "identifier:ci-bot": - role: deployer scopes: [staging] -
Set environment variables for bearer tokens
export SCOTTY__API__BEARER_TOKENS__CI_BOT=$(openssl rand -base64 32) -
Restart Scotty - No breaking changes to existing OAuth users!
-
Update CI/CD pipelines to use bearer tokens
# .gitlab-ci.yml example deploy: script: - export SCOTTY_ACCESS_TOKEN="${CI_BOT_TOKEN}" - scottyctl app:create ... -
Test both authentication paths
# Test OAuth (human) scottyctl auth:login scottyctl app:list # Test bearer token (service account) export SCOTTY_ACCESS_TOKEN="${CI_BOT_TOKEN}" scottyctl app:list
Zero Downtime: Existing OAuth users continue working without any changes. Service accounts can be added incrementally.
Jump to heading Troubleshooting
Issue: Service account bearer token not working
Error: 401 Unauthorized
Diagnosis:
-
Check token is correctly set:
echo $SCOTTY_ACCESS_TOKEN # Should show token value -
Check identifier exists in policy.yaml:
assignments: "identifier:ci-bot": # Must match bearer_tokens key - role: deployer scopes: [staging] -
Check bearer_tokens configuration:
# Verify token mapping curl http://localhost:21342/api/v1/info # Should not show token values -
Check logs with debug mode:
RUST_LOG=debug cargo run --bin scotty # Look for: "Bearer token authentication successful" or "OAuth authentication successful" -
Verify expected behavior matches tests:
# See test_oauth_bearer_token_fallback_with_valid_token in: # scotty/src/api/bearer_auth_tests.rs:233-257 cargo test test_oauth_bearer_token_fallback_with_valid_token -- --nocapture
Issue: OAuth users can't authenticate
Error: OAuth validation failed
Diagnosis:
- Verify OAuth configuration is still correct (client_id, client_secret, etc.)
- Check OIDC provider is accessible
- Verify redirect URLs match
Issue: Unclear which authentication method was used
Solution: Check Scotty logs - authentication method is logged:
DEBUG Bearer token authentication successful
DEBUG OAuth authentication successful
Jump to heading When to Use Hybrid Authentication
Use Hybrid Mode When:
- ✅ You have both human users (web UI) and service accounts (CI/CD)
- ✅ You want centralized authentication with OIDC
- ✅ You need service accounts with zero OAuth latency
- ✅ You want fine-grained RBAC for different actors
Use Pure OAuth When:
- ✅ Only human users access Scotty
- ✅ All access is via web UI or CLI with device flow
- ✅ No automation or service accounts needed
Use Pure Bearer Mode When:
- ✅ Only service accounts/automation access Scotty
- ✅ No human interactive access needed
- ✅ Simpler deployment without OAuth infrastructure
See Configuration Documentation for complete configuration reference and config/README.md for detailed security best practices.
Jump to heading Frontend Integration
The Scotty frontend automatically detects OAuth mode and provides:
Jump to heading Login Flow
- Login page shows "Continue with OAuth" button
- OAuth callback page handles the return from OIDC provider
- User info component displays authenticated user with logout option
Jump to heading Token Management
- Automatic token storage in browser sessionStorage
- Token validation on each API request
- Automatic logout on token expiration or validation failure
Jump to heading CLI Integration (scottyctl)
For CLI usage with OAuth-enabled Scotty, you have two options:
Jump to heading Device Flow (Recommended)
# Use OAuth device flow for CLI authentication
scottyctl login --server http://localhost:21342
Jump to heading Manual Token
# Extract token from browser sessionStorage and use manually
export SCOTTY_ACCESS_TOKEN=your_oauth_token
scottyctl --server http://localhost:21342 app:list
Jump to heading Security Features
Jump to heading PKCE (Proof Key for Code Exchange)
- Enhanced security for public clients (SPAs) - prevents authorization code interception attacks
- SHA256 code challenge - cryptographically secure random verifier with SHA256 hashing
- Protected storage - PKCE verifier stored in memory using
MaskedSecrettype (protected from memory dumps and logs) - Single-use verification - verifier is removed from session store after successful token exchange
Jump to heading CSRF Protection
- State parameter validation - combines session ID and CSRF token (
session_id:csrf_tokenformat) - Session-based tracking - each OAuth flow gets a unique session with secure token storage
- Automatic cleanup - expired sessions removed every 5 minutes by background task
- Time-limited sessions - web flow sessions expire after 10 minutes, OAuth sessions after 5 minutes
Jump to heading Token Security
- Token validation on every request - authentication middleware validates tokens before allowing access to protected endpoints
- OIDC provider validation - OAuth tokens validated against OIDC provider's userinfo endpoint
- Session expiration - OAuth sessions expire after 5 minutes of token exchange
- Memory protection - CSRF tokens and PKCE verifiers stored using
MaskedSecrettype with automatic zeroization - Secure storage - tokens stored in browser sessionStorage (cleared when tab closes)
Jump to heading Troubleshooting
Jump to heading Common Issues
Redirect URI Mismatch
Error: redirect_uri mismatch in OIDC provider
- Ensure OIDC provider OAuth app redirect URI exactly matches Scotty configuration
- Check for trailing slashes, HTTP vs HTTPS, and port numbers
- Verify the redirect URI is
http://localhost:21342/api/oauth/callback
Invalid Client Credentials
Error: Invalid client credentials
- Verify
client_idandclient_secretmatch OIDC provider OAuth application - Ensure credentials are correctly set in configuration or environment variables
PKCE Validation Failed
Error: PKCE code challenge validation failed
- This indicates a potential security issue or session corruption
- Clear browser data and retry the authentication flow
Session Expired
Error: OAuth session not found or expired
- OAuth sessions have a limited lifetime
- Restart the authentication flow from the beginning
Jump to heading Debug Commands
# Check Scotty configuration
curl http://localhost:21342/api/v1/info
# Test OAuth endpoints
curl -I http://localhost:21342/oauth/authorize
# Verify authentication (with valid token)
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:21342/api/v1/authenticated/apps
Jump to heading Debug Logging
Enable debug logging to troubleshoot OAuth issues:
RUST_LOG=debug cargo run --bin scotty
Jump to heading URLs and Access
- Application: http://localhost:21342
- OAuth Authorization: http://localhost:21342/oauth/authorize
- OAuth Callback: http://localhost:21342/api/oauth/callback
- OAuth Session Exchange: http://localhost:21342/oauth/exchange
- API Documentation: http://localhost:21342/rapidoc
- Health Check: http://localhost:21342/api/v1/health (public)