OAuth Integration Guide
Complete guide for integrating OAuth/SSO authentication in your application.
Complete guide for integrating OAuth/SSO authentication in your application.
Table of Contents
- Quick Start
- Setup for Each Provider
- Frontend Integration
- Security Best Practices
- Production Checklist
Quick Start
1. Configure OAuth Provider in Volcano
# Example: Configure Google OAuth
curl -X POST https://api.volcano.dev/projects/YOUR_PROJECT_ID/oauth/configs \
-H "Authorization: Bearer YOUR_PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "google",
"client_id": "123456789.apps.googleusercontent.com",
"client_secret": "GOCSPX-your-secret-here",
"redirect_url": "https://yourapp.com/auth/callback",
"scopes": ["openid", "email", "profile"]
}'2. Add Sign-In Button to Your Frontend
<button onclick="signInWithGoogle()">Sign in with Google</button>
<script>
function signInWithGoogle() {
window.location.href = 'https://api.volcano.dev/auth/oauth/google/authorize';
}
</script>3. Handle Callback
When Google redirects back to your callback URL, Volcano automatically:
- Exchanges authorization code for access token
- Fetches user info from Google
- Creates/signs in user
- Returns Volcano access_token + refresh_token
Setup for Each Provider
Google OAuth
Step 1: Create OAuth App
- Go to Google Cloud Console
- Create or select a project
- Navigate to APIs & Services → Credentials
- Click Create Credentials → OAuth 2.0 Client ID
- Select Web application
- Add Authorized redirect URIs:
- Development:
http://localhost:8000/auth/oauth/google/callback - Production:
https://api.volcano.dev/auth/oauth/google/callback
- Development:
- Click Create
- Copy Client ID and Client secret
Step 2: Enable Google+ API
- Navigate to APIs & Services → Library
- Search for Google+ API
- Click Enable
Step 3: Configure in Volcano
curl -X POST https://api.volcano.dev/projects/YOUR_PROJECT_ID/oauth/configs \
-H "Authorization: Bearer YOUR_PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "google",
"client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
"client_secret": "YOUR_CLIENT_SECRET",
"redirect_url": "https://yourapp.com/auth/callback",
"scopes": ["openid", "email", "profile"]
}'Default Scopes:
openid- OpenID Connectemail- User's email addressprofile- User's basic profile (name, picture)
GitHub OAuth
Step 1: Create OAuth App
- Go to GitHub Settings → OAuth Apps
- Click New OAuth App
- Fill in:
- Application name: Your app name
- Homepage URL:
https://yourapp.com - Authorization callback URL:
- Development:
http://localhost:8000/auth/oauth/github/callback - Production:
https://api.volcano.dev/auth/oauth/github/callback
- Development:
- Click Register application
- Copy Client ID
- Click Generate a new client secret
- Copy Client secret
Step 2: Configure in Volcano
curl -X POST https://api.volcano.dev/projects/YOUR_PROJECT_ID/oauth/configs \
-H "Authorization: Bearer YOUR_PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "github",
"client_id": "YOUR_GITHUB_CLIENT_ID",
"client_secret": "YOUR_GITHUB_CLIENT_SECRET",
"redirect_url": "https://yourapp.com/auth/callback",
"scopes": ["read:user", "user:email"]
}'Default Scopes:
read:user- Read user profileuser:email- Access user's email addresses
Note: GitHub users can hide their email - handle this case in your app.
Microsoft OAuth
Step 1: Register App in Azure
- Go to Azure Portal
- Navigate to Azure Active Directory → App registrations
- Click New registration
- Fill in:
- Name: Your app name
- Supported account types: Choose based on needs:
- Personal Microsoft accounts only
- Work/school accounts only
- Both (recommended)
- Redirect URI: Web platform
- Development:
http://localhost:8000/auth/oauth/microsoft/callback - Production:
https://api.volcano.dev/auth/oauth/microsoft/callback
- Development:
- Click Register
- Copy Application (client) ID
Step 2: Create Client Secret
- Go to Certificates & secrets
- Click New client secret
- Add description and expiration
- Click Add
- Copy the secret value immediately (shown only once!)
Step 3: Configure in Volcano
curl -X POST https://api.volcano.dev/projects/YOUR_PROJECT_ID/oauth/configs \
-H "Authorization: Bearer YOUR_PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "microsoft",
"client_id": "YOUR_APPLICATION_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET_VALUE",
"redirect_url": "https://yourapp.com/auth/callback",
"scopes": ["openid", "email", "profile"]
}'Default Scopes:
openid- OpenID Connectemail- User's emailprofile- User's profile info
Apple Sign In
Step 1: Configure Sign in with Apple
- Go to Apple Developer → Certificates, IDs & Profiles
- Click Identifiers → + (Add)
- Select Services IDs → Continue
- Fill in:
- Description: Your app name
- Identifier: Reverse DNS (e.g.,
com.yourapp.signin)
- Click Continue → Register
Step 2: Enable Sign in with Apple
- Click on your Services ID
- Check Sign in with Apple
- Click Configure
- Add Website URLs:
- Domains:
yourapp.com,api.volcano.dev - Return URLs:
https://yourapp.com/auth/callbackhttps://api.volcano.dev/auth/oauth/apple/callback
- Domains:
- Click Done → Save
Step 3: Create Key
- Go to Keys → + (Add)
- Key Name: Your app name
- Check Sign in with Apple
- Click Configure → Select your App ID
- Click Save → Continue → Register
- Download the
.p8file (private key) - Note the Key ID
Step 4: Configure in Volcano
curl -X POST https://api.volcano.dev/projects/YOUR_PROJECT_ID/oauth/configs \
-H "Authorization: Bearer YOUR_PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "apple",
"client_id": "com.yourapp.signin",
"client_secret": "YOUR_APPLE_KEY_CONTENT",
"redirect_url": "https://yourapp.com/auth/callback",
"scopes": ["email", "name"]
}'Default Scopes:
email- User's email (might be Apple relay)name- User's name (only on first auth)
Note: Apple configuration is more complex - see Apple documentation for details.
Frontend Integration
Basic Implementation
<!DOCTYPE html>
<html>
<head>
<title>OAuth Example</title>
</head>
<body>
<!-- OAuth Buttons -->
<button onclick="signInWithGoogle()">Sign in with Google</button>
<button onclick="signInWithGitHub()">Sign in with GitHub</button>
<button onclick="signInWithMicrosoft()">Sign in with Microsoft</button>
<button onclick="signInWithApple()">Sign in with Apple</button>
<script>
const PROJECT_ID = 'your-project-id';
const API_URL = 'https://api.volcano.dev';
function signInWithGoogle() {
redirectToOAuth('google');
}
function signInWithGitHub() {
redirectToOAuth('github');
}
function signInWithMicrosoft() {
redirectToOAuth('microsoft');
}
function signInWithApple() {
redirectToOAuth('apple');
}
function redirectToOAuth(provider) {
const authUrl = `${API_URL}/auth/oauth/${provider}/authorize`;
window.location.href = authUrl;
}
</script>
</body>
</html>Advanced: Provider Linking
Allow users to link multiple authentication methods:
// User is already logged in
const accessToken = localStorage.getItem('access_token');
// Link Google to existing account
async function linkGoogle() {
const response = await fetch(
`${API_URL}/auth/oauth/google/link`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`
}
}
);
const data = await response.json();
if (response.ok) {
// Redirect to OAuth authorization
window.location.href = data.authorization_url;
} else {
console.error('Error:', data.error);
}
}
// After OAuth completes, provider is linked
// User can now sign in with either password OR GoogleAdvanced: Unlink Provider
async function unlinkGoogle() {
const accessToken = localStorage.getItem('access_token');
const response = await fetch(
`${API_URL}/auth/oauth/google/unlink`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${accessToken}`
}
}
);
if (response.ok) {
console.log('Google unlinked successfully');
} else {
const data = await response.json();
console.error('Error:', data.error);
// Might be: "cannot unlink last authentication method"
}
}Advanced: List Linked Providers
async function getLinkedProviders() {
const accessToken = localStorage.getItem('access_token');
const response = await fetch(
`${API_URL}/auth/oauth/providers`,
{
headers: {
'Authorization': `Bearer ${accessToken}`
}
}
);
const data = await response.json();
// data.providers = [
// { provider: 'google', linked_at: '...', updated_at: '...' },
// { provider: 'github', linked_at: '...', updated_at: '...' }
// ]
return data.providers;
}Security Best Practices
1. Always Use HTTPS in Production
// GOOD
const REDIRECT_URL = 'https://yourapp.com/auth/callback';
// BAD (production)
const REDIRECT_URL = 'http://yourapp.com/auth/callback';HTTP is only acceptable for local development.
2. Validate Redirect URLs
Volcano automatically validates redirect URLs to prevent open redirects:
// Valid redirect URLs
'https://yourapp.com/callback'
'https://app.yourapp.com/auth/callback'
'http://localhost:8080/callback' // Dev only
// Invalid (blocked by Volcano)
'http://evil.com@yourapp.com/callback' // Contains @
'javascript:alert(1)' // Invalid protocol3. Store Tokens Securely
// GOOD: Use localStorage or sessionStorage
localStorage.setItem('access_token', token);
// BETTER: Use httpOnly cookies (if possible)
document.cookie = `access_token=${token}; Secure; HttpOnly; SameSite=Strict`;
// BAD: Global variables
window.myAccessToken = token;
// BAD: URL parameters
window.location.href = `/dashboard?token=${token}`;4. Handle Email Conflicts
When OAuth email already exists:
// User tries to sign up with Google
// But email already exists from password signup
fetch('/auth/oauth/google/authorize')
.then(response => {
if (response.status === 409) {
// Show message: "Email already exists. Please sign in and link Google."
showMessage('Please sign in with your password, then link Google from settings');
}
});5. Prevent Account Lockout
// Before unlinking provider, check if it's the last method
async function unlinkProvider(provider) {
const response = await fetch(`/auth/oauth/${provider}/unlink`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${accessToken}` }
});
if (response.status === 400) {
const data = await response.json();
if (data.error.includes('last authentication method')) {
alert('You need to set a password before unlinking this provider');
return;
}
}
// Successfully unlinked
}Production Checklist
OAuth Provider Setup
- Register OAuth app with provider
- Set production redirect URLs (HTTPS only)
- Copy client_id and client_secret
- Enable required APIs (Google+ for Google)
- Test OAuth flow in provider's test environment
Volcano Configuration
- Configure OAuth via API
- Verify redirect_url matches exactly
- Test each provider's OAuth flow
- Verify secrets are not exposed in API responses
- Enable OAuth config (enabled: true)
Frontend Implementation
- Add OAuth buttons for each provider
- Implement OAuth redirect logic
- Handle callback and token storage
- Implement error handling
- Test email conflict scenario
- Test provider linking/unlinking
- Implement "cannot unlink last method" UI
Security
- Use HTTPS for all redirect URLs
- Validate state parameter (automatic in Volcano)
- Store tokens securely (httpOnly cookies preferred)
- Implement token refresh on 401
- Add CSRF protection to your forms
- Rate limit OAuth endpoints if needed
- Monitor OAuth callback failures
Testing
- Test OAuth signup (new user)
- Test OAuth signin (existing user)
- Test email conflict (manual linking required)
- Test provider linking
- Test provider unlinking
- Test unlinking last method (should fail)
- Test disabled provider (should fail)
- Test with multiple providers
- Test cross-project isolation
Monitoring
- Monitor OAuth callback success rate
- Alert on high OAuth error rates
- Track which providers are most used
- Monitor email conflict rates
- Track provider linking/unlinking
Common Integration Patterns
Pattern 1: OAuth-Only App
App that only uses OAuth (no email/password):
// Only show OAuth buttons
<button onclick="signInWithGoogle()">Sign in with Google</button>
<button onclick="signInWithGitHub()">Sign in with GitHub</button>
// No email/password form needed
// Users MUST have at least one OAuth providerConfiguration:
{
"enable_signup": false // Disable email/password signup
}Pattern 2: Hybrid App
App with both email/password AND OAuth:
// Email/password form
<input type="email" id="email">
<input type="password" id="password">
<button onclick="signUp()">Sign Up</button>
// OR divider
<div>- OR -</div>
// OAuth buttons
<button onclick="signInWithGoogle()">Sign in with Google</button>
<button onclick="signInWithGitHub()">Sign in with GitHub</button>Users can:
- Sign up with email/password
- Sign up with OAuth
- Link OAuth to existing password account
Pattern 3: Enterprise SSO
Enterprise apps that require specific OAuth provider:
// Only show Microsoft button for enterprise users
if (userDomain === 'company.com') {
showMicrosoftButton();
} else {
showAllProviders();
}Configuration: Enable only specific providers per project.
Troubleshooting
"OAuth provider not configured"
Cause: Provider not set up in Volcano
Solution:
# Check existing configs
curl -H "Authorization: Bearer TOKEN" \
https://api.volcano.dev/projects/PROJECT_ID/oauth/configs
# Create config if missing
curl -X POST ... (see setup section)"invalid state parameter"
Causes:
- State expired (10 min timeout)
- State already used (one-time use)
- CSRF attack attempt
Solution: Restart OAuth flow from beginning
"email already exists - please link provider to existing account"
Cause: User's OAuth email matches existing account
Flow:
- User tries: Sign in with Google
- Error: Email already exists
- User signs in with password
- User clicks "Link Google" in settings
- Success: Can now use either method
Frontend handling:
if (response.status === 409) {
showMessage(
'Email already exists. ' +
'<a href="/signin">Sign in</a> and link Google from your profile.'
);
}OAuth redirect doesn't work
Common causes:
-
Redirect URL mismatch
- Check: Provider settings exactly match Volcano config
- Example:
https://yourapp.com/callbackvshttps://yourapp.com/auth/callback
-
Missing HTTPS
- Most providers require HTTPS in production
- Use
http://localhostfor development only
-
Wrong domain
- Redirect URL must match authorized domain in provider settings
"OAuth provider is disabled"
Cause: Provider was disabled in config
Solution:
curl -X PUT https://api.volcano.dev/projects/PROJECT_ID/oauth/configs/google \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'GitHub doesn't return email
Cause: User has email privacy enabled on GitHub
Solutions:
- Request
user:emailscope (already default) - Ask user to provide email manually after OAuth
- Handle missing email gracefully in your app
if (!user.email) {
// Ask user to provide email
promptForEmail();
}Testing OAuth Locally
1. Use ngrok for Local Testing
# Start ngrok
ngrok http 8000
# Use ngrok URL in OAuth configs
# Example: https://abc123.ngrok.io/auth/oauth/google/callback2. Use localhost (if provider supports)
Some providers allow http://localhost for testing:
| Provider | Localhost Support |
|---|---|
| Yes | |
| GitHub | Yes |
| Microsoft | Sometimes |
| Apple | No (requires HTTPS) |
Advanced Topics
Multiple Projects, Same Provider
Each project has independent OAuth configuration:
// Project A - Google OAuth
{
"project_id": "project-a",
"provider": "google",
"client_id": "project-a-google-id",
"redirect_url": "https://app-a.com/callback"
}
// Project B - Google OAuth (different app)
{
"project_id": "project-b",
"provider": "google",
"client_id": "project-b-google-id",
"redirect_url": "https://app-b.com/callback"
}User with Same Email in Multiple Projects
OAuth follows same isolation as email/password auth:
- User A in Project 1:
john@example.comvia Google - User B in Project 2:
john@example.comvia password - These are completely independent accounts
- Different user IDs, different passwords/providers
Hybrid Authentication
User can have multiple auth methods:
User Account:
├── Password: Yes
├── Google: Linked
└── GitHub: Linked
Can sign in with:
- Email + password
- "Sign in with Google"
- "Sign in with GitHub"Resources
Need Help?
- Check troubleshooting guide
- Review security audit
- Open an issue on GitHub