OAuth API reference
API reference for OAuth/SSO provider authentication.
API reference for OAuth/SSO provider authentication.
Overview
Volcano supports OAuth 2.0 authentication with major identity providers:
- GitHub
- Microsoft (Azure AD / Personal accounts)
- Apple Sign In
Endpoints
Public OAuth flow
Start OAuth authorization
GET /auth/oauth/{provider}/authorizeRedirects user to OAuth provider for authorization.
Path Parameters:
project_id- UUID of your projectprovider- One of:google,github,microsoft,apple
Query Parameters (Optional):
redirect_url- URL to redirect after OAuth completeslink_user_id- User ID if linking to existing account (internal use)
Response: 307 Temporary Redirect to OAuth provider
Errors:
404- OAuth provider not configured400- OAuth provider is disabled
Example:
curl -i http://localhost:8000/auth/oauth/google/authorizeHandle OAuth callback
GET /auth/oauth/{provider}/callback?code={code}&state={state}OAuth provider calls this endpoint after user authorization.
Path Parameters:
project_id- UUID of your projectprovider- OAuth provider used
Query Parameters:
code- Authorization code from provider (required)state- CSRF protection state (required)error- Error code if authorization failed
Response:
200 OK- Existing user signed in201 Created- New user created
Response Body:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "refresh_abc123...",
"user": {
"id": "uuid",
"email": "user@example.com",
"email_confirmed": true,
"raw_user_meta_data": {
"name": "John Doe",
"picture": "https://...",
"provider": "google",
"email_verified": true
},
"status": "active",
"created_at": "2026-01-03T10:00:00Z"
}
}Errors:
400- Missing/invalid code or state400- State parameter expired (10 min timeout)409- Email already exists (requires linking)
User OAuth management
List linked providers
GET /auth/oauth/providers
Authorization: Bearer {access_token}Get list of OAuth providers linked to current user.
Headers:
Authorization- Bearer token (user's access token)
Response:
{
"providers": [
{
"provider": "google",
"linked_at": "2026-01-03T10:00:00Z",
"updated_at": "2026-01-03T10:00:00Z"
},
{
"provider": "github",
"linked_at": "2026-01-05T14:30:00Z",
"updated_at": "2026-01-05T14:30:00Z"
}
]
}Errors:
401- Not authenticated or invalid token
Link OAuth provider
POST /auth/oauth/{provider}/link
Authorization: Bearer {access_token}Link OAuth provider to current authenticated user.
Path Parameters:
provider- Provider to link:google,github,microsoft,apple
Headers:
Authorization- Bearer token (user's access token)
Response:
{
"authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?..."
}User should be redirected to authorization_url. After authorization, provider will be linked.
Errors:
401- Not authenticated404- OAuth provider not configured409- Provider already linked400- Provider is disabled
Unlink OAuth provider
DELETE /auth/oauth/{provider}/unlink
Authorization: Bearer {access_token}Remove OAuth provider from user's account.
Path Parameters:
provider- Provider to unlink
Headers:
Authorization- Bearer token (user's access token)
Response: 204 No Content
Errors:
401- Not authenticated404- Provider not linked400- Cannot unlink last authentication method
Security: Users cannot remove their only authentication method. They must have a password OR another OAuth provider.
Admin OAuth configuration
List OAuth configurations
GET /projects/{project_id}/oauth/configs
Authorization: Bearer {platform_token}List all OAuth configurations for a project.
Headers:
Authorization- Bearer token (platform admin token)
Response:
{
"configs": [
{
"id": "uuid",
"provider": "google",
"client_id": "123456.apps.googleusercontent.com",
"enabled": true,
"redirect_url": "https://yourapp.com/callback",
"scopes": ["openid", "email", "profile"],
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}
]
}Note: client_secret is NOT included for security.
Get OAuth configuration
GET /projects/{project_id}/oauth/configs/{provider}
Authorization: Bearer {platform_token}Get specific OAuth configuration.
Response:
{
"id": "uuid",
"provider": "google",
"client_id": "123456.apps.googleusercontent.com",
"client_secret": "GOCS...****", // Masked
"enabled": true,
"redirect_url": "https://yourapp.com/callback",
"scopes": ["openid", "email", "profile"],
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}Note: Secret is masked (first 4 + last 4 chars shown).
Create OAuth configuration
POST /projects/{project_id}/oauth/configs
Authorization: Bearer {platform_token}
Content-Type: application/json
{
"provider": "google",
"client_id": "123456.apps.googleusercontent.com",
"client_secret": "GOCSPX-your-secret",
"redirect_url": "https://yourapp.com/auth/callback",
"scopes": ["openid", "email", "profile"]
}Request Fields:
provider- Required:google,github,microsoft,apple,deviceclient_id- Required: Client identifierclient_secret- Required for non-device providers, not used fordeviceredirect_url- Required for non-device providers, not used fordevicescopes- Optional for non-device providers (uses defaults), not used fordevice
For provider=device (RFC8628 CLI/device flow), a minimal payload is:
{
"provider": "device",
"client_id": "myapp-cli"
}Custom verification page: the device-approval page is configured on the project's auth config, not on the device client. Set
device_verification_urlviaPATCH /auth/configto point device logins at your own RFC 8628 page; otherwise the managed device page is used. See Auth endpoints and Custom verification page.
Response: 201 Created with OAuth configuration (secret masked)
Errors:
400- Invalid provider or redirect URL409- Provider already configured
Redirect URL validation:
| URL | Valid | Reason |
|---|---|---|
https://yourapp.com/callback | Yes | HTTPS with valid domain |
http://localhost:8080/callback | Yes | Development only |
http://evil.com@yourapp.com/callback | No | Contains @ character |
javascript:alert(1) | No | Invalid protocol |
Update OAuth configuration
PUT /projects/{project_id}/oauth/configs/{provider}
Authorization: Bearer {platform_token}
Content-Type: application/json
{
"client_id": "new-client-id",
"client_secret": "new-secret",
"redirect_url": "https://newapp.com/callback",
"scopes": ["openid", "email"],
"enabled": false
}All fields are optional. Only provided fields will be updated.
For provider=device, use query parameter client_id to identify which device client to update:
PUT /projects/{project_id}/oauth/configs/device?client_id=myapp-cliDevice clients only support updating:
client_idenabled
client_secret, redirect_url, and scopes are not supported for provider=device.
Response: 200 OK with updated configuration
Delete OAuth configuration
DELETE /projects/{project_id}/oauth/configs/{provider}
Authorization: Bearer {platform_token}Remove OAuth configuration for a provider.
For provider=device, use query parameter client_id to identify which device client to delete:
DELETE /projects/{project_id}/oauth/configs/device?client_id=myapp-cliResponse: 204 No Content
List available providers
GET /projects/{project_id}/oauth/providers
Authorization: Bearer {platform_token}Get list of supported OAuth providers and their default scopes.
Response:
{
"providers": [
{
"id": "google",
"name": "Google",
"default_scopes": ["openid", "email", "profile"]
},
{
"id": "github",
"name": "GitHub",
"default_scopes": ["read:user", "user:email"]
},
{
"id": "microsoft",
"name": "Microsoft",
"default_scopes": ["openid", "email", "profile"]
},
{
"id": "apple",
"name": "Apple",
"default_scopes": ["email", "name"]
}
]
}Security
CSRF protection
OAuth flow uses state parameter to prevent CSRF attacks:
- State is randomly generated (32 bytes, base64 encoded)
- State is verified in callback
- State expires after 10 minutes
- State is one-time use only
Open redirect prevention
Redirect URLs are validated:
- Must use
httporhttpsprotocol - Cannot contain
@character (preventshttp://evil.com@yourapp.com) - No wildcards allowed
Client secret protection
- Secrets are encrypted with AES-256-GCM before database storage
- Automatic encryption/decryption in database layer
- Secrets are masked in API responses
- Secrets not included in list endpoints
- Only first 4 and last 4 characters shown in GET requests
- Requires ENCRYPTION_KEY environment variable (32 bytes)
Email conflict handling
If OAuth email already exists:
- Returns
409 Conflicterror - User must sign in with password
- User can then link OAuth provider via
/linkendpoint - Prevents account takeover
Last authentication method
Users cannot remove their only authentication method:
- If user has ONLY OAuth (no password) → cannot unlink
- If user has password AND OAuth → can unlink OAuth
- If user has multiple OAuth → can unlink any single one
- Prevents account lockout
Provider differences
- User ID: Numeric string
- Email: Always provided
- Email verified: Yes
- Scopes:
openid,email,profile
GitHub
- User ID: Numeric
- Email: Might be private (user setting)
- Email verified: Assumed true if provided
- Scopes:
read:user,user:email
Microsoft
- User ID: UUID
- Email: From
mailoruserPrincipalName - Email verified: Assumed true
- Works with: Personal accounts AND Azure AD
- Scopes:
openid,email,profile
Apple
- User ID: Stable subject identifier
- Email: Might be Apple relay address
- Email verified: Explicitly provided
- Name: Only sent on first authorization
- Scopes:
email,name
Error codes
| Code | Meaning | Solution |
|---|---|---|
| 400 | Bad request (invalid params) | Check request parameters |
| 401 | Not authenticated | Provide valid access token |
| 404 | Provider not configured | Configure OAuth provider first |
| 409 | Conflict (duplicate) | Provider already exists or email conflict |
Examples
See complete examples: