# This request is typically made by the OAuth provider
curl -X GET "http://localhost:8080/callback?code=auth_code_here&state=csrf_state&provider=google"
// Handle OAuth callback in your application
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
const error = urlParams.get('error');
if (error) {
console.error('OAuth error:', error);
return;
}
if (code && state) {
// OAuth callback will be handled automatically by the auth service
// The user will be redirected to the specified redirect_to URL
console.log('OAuth callback received, processing...');
}
}, []);
# Handle OAuth callback in Flask
from flask import request, redirect
@app.route('/oauth/callback')
def oauth_callback():
code = request.args.get('code')
state = request.args.get('state')
error = request.args.get('error')
if error:
return f"OAuth error: {error}", 400
if not code:
return "Missing authorization code", 400
# The auth service will handle the token exchange
# and redirect the user appropriately
return redirect('/dashboard')
package main
import (
"fmt"
"net/http"
)
func oauthCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
errorParam := r.URL.Query().Get("error")
if errorParam != "" {
http.Error(w, fmt.Sprintf("OAuth error: %s", errorParam), http.StatusBadRequest)
return
}
if code == "" {
http.Error(w, "Missing authorization code", http.StatusBadRequest)
return
}
// Auth service handles token exchange and user creation
http.Redirect(w, r, "/dashboard", http.StatusFound)
}
HTTP/1.1 302 Found
Location: https://yourapp.com/dashboard?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&expires_in=3600&token_type=bearer
Set-Cookie: sb-access-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Secure; SameSite=Lax
Set-Cookie: sb-refresh-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Secure; SameSite=Lax
OAuth
OAuth Callback
Handle OAuth provider callback and complete authentication
GET
/
callback
# This request is typically made by the OAuth provider
curl -X GET "http://localhost:8080/callback?code=auth_code_here&state=csrf_state&provider=google"
// Handle OAuth callback in your application
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
const error = urlParams.get('error');
if (error) {
console.error('OAuth error:', error);
return;
}
if (code && state) {
// OAuth callback will be handled automatically by the auth service
// The user will be redirected to the specified redirect_to URL
console.log('OAuth callback received, processing...');
}
}, []);
# Handle OAuth callback in Flask
from flask import request, redirect
@app.route('/oauth/callback')
def oauth_callback():
code = request.args.get('code')
state = request.args.get('state')
error = request.args.get('error')
if error:
return f"OAuth error: {error}", 400
if not code:
return "Missing authorization code", 400
# The auth service will handle the token exchange
# and redirect the user appropriately
return redirect('/dashboard')
package main
import (
"fmt"
"net/http"
)
func oauthCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
errorParam := r.URL.Query().Get("error")
if errorParam != "" {
http.Error(w, fmt.Sprintf("OAuth error: %s", errorParam), http.StatusBadRequest)
return
}
if code == "" {
http.Error(w, "Missing authorization code", http.StatusBadRequest)
return
}
// Auth service handles token exchange and user creation
http.Redirect(w, r, "/dashboard", http.StatusFound)
}
HTTP/1.1 302 Found
Location: https://yourapp.com/dashboard?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&expires_in=3600&token_type=bearer
Set-Cookie: sb-access-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Secure; SameSite=Lax
Set-Cookie: sb-refresh-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Secure; SameSite=Lax
Handle OAuth provider callbacks and complete the authentication flow. This endpoint receives the authorization code from OAuth providers and exchanges it for access tokens to authenticate users.
This endpoint is automatically called by OAuth providers after user authorization. You typically don’t call this endpoint directly.
# This request is typically made by the OAuth provider
curl -X GET "http://localhost:8080/callback?code=auth_code_here&state=csrf_state&provider=google"
// Handle OAuth callback in your application
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
const error = urlParams.get('error');
if (error) {
console.error('OAuth error:', error);
return;
}
if (code && state) {
// OAuth callback will be handled automatically by the auth service
// The user will be redirected to the specified redirect_to URL
console.log('OAuth callback received, processing...');
}
}, []);
# Handle OAuth callback in Flask
from flask import request, redirect
@app.route('/oauth/callback')
def oauth_callback():
code = request.args.get('code')
state = request.args.get('state')
error = request.args.get('error')
if error:
return f"OAuth error: {error}", 400
if not code:
return "Missing authorization code", 400
# The auth service will handle the token exchange
# and redirect the user appropriately
return redirect('/dashboard')
package main
import (
"fmt"
"net/http"
)
func oauthCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
errorParam := r.URL.Query().Get("error")
if errorParam != "" {
http.Error(w, fmt.Sprintf("OAuth error: %s", errorParam), http.StatusBadRequest)
return
}
if code == "" {
http.Error(w, "Missing authorization code", http.StatusBadRequest)
return
}
// Auth service handles token exchange and user creation
http.Redirect(w, r, "/dashboard", http.StatusFound)
}
Query Parameters
string
required
Authorization code returned by the OAuth provider.
string
State parameter for CSRF protection (if provided in the authorization request).
string
OAuth provider identifier (may be included in some implementations).
string
Error code if the OAuth authorization failed.
string
Human-readable error description if the OAuth authorization failed.
Response
This endpoint typically returns a302 Found redirect response to the application’s redirect URL with authentication tokens.
string
Redirect URL with authentication tokens or session information
HTTP/1.1 302 Found
Location: https://yourapp.com/dashboard?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&expires_in=3600&token_type=bearer
Set-Cookie: sb-access-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Secure; SameSite=Lax
Set-Cookie: sb-refresh-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Secure; SameSite=Lax
Error Responses
HTTP/1.1 302 Found
Location: https://yourapp.com/login?error=access_denied&error_description=User%20denied%20access
{
"code": 400,
"msg": "Invalid authorization code",
"details": "The authorization code is invalid or has expired"
}
{
"code": 400,
"msg": "State parameter mismatch",
"details": "The state parameter does not match the expected value"
}
{
"code": 500,
"msg": "OAuth provider error",
"details": "Failed to exchange authorization code for access token"
}
OAuth Callback Flow
Successful Authentication
- Authorization Code Exchange: The auth service exchanges the authorization code for access tokens
- User Information Retrieval: Fetch user profile information from the OAuth provider
- User Account Management: Create or update user account in the database
- Session Creation: Generate authentication tokens for the user
- Redirect: Redirect user to the specified redirect URL with tokens
Error Handling
Common OAuth errors and their handling:- access_denied: User denied authorization
- invalid_request: Malformed authorization request
- unauthorized_client: Client not authorized for this grant type
- unsupported_response_type: Authorization server doesn’t support the response type
- invalid_scope: Requested scope is invalid or unknown
- server_error: Authorization server encountered an error
- temporarily_unavailable: Authorization server is temporarily unavailable
Implementation Examples
React OAuth Callback Handler
import { useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
function OAuthCallback() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [status, setStatus] = useState('processing');
const [error, setError] = useState(null);
useEffect(() => {
handleOAuthCallback();
}, []);
const handleOAuthCallback = async () => {
const code = searchParams.get('code');
const state = searchParams.get('state');
const error = searchParams.get('error');
const errorDescription = searchParams.get('error_description');
// Handle OAuth errors
if (error) {
setStatus('error');
setError({
code: error,
description: errorDescription || 'OAuth authentication failed'
});
return;
}
// Validate required parameters
if (!code) {
setStatus('error');
setError({
code: 'missing_code',
description: 'Authorization code is missing'
});
return;
}
// Validate state parameter (CSRF protection)
const storedState = sessionStorage.getItem('oauth_state');
if (state && storedState && state !== storedState) {
setStatus('error');
setError({
code: 'state_mismatch',
description: 'State parameter mismatch - possible CSRF attack'
});
return;
}
try {
// Extract tokens from URL or cookies
const accessToken = searchParams.get('access_token');
const refreshToken = searchParams.get('refresh_token');
const expiresIn = searchParams.get('expires_in');
if (accessToken) {
// Store tokens
localStorage.setItem('access_token', accessToken);
if (refreshToken) {
localStorage.setItem('refresh_token', refreshToken);
}
if (expiresIn) {
const expiresAt = Date.now() + parseInt(expiresIn) * 1000;
localStorage.setItem('token_expires_at', expiresAt.toString());
}
// Clear OAuth state
sessionStorage.removeItem('oauth_state');
setStatus('success');
// Redirect to dashboard or intended page
const redirectTo = sessionStorage.getItem('oauth_redirect_to') || '/dashboard';
sessionStorage.removeItem('oauth_redirect_to');
setTimeout(() => {
navigate(redirectTo, { replace: true });
}, 1000);
} else {
// Tokens might be in cookies, check if user is authenticated
const response = await fetch('/api/user/profile', {
credentials: 'include'
});
if (response.ok) {
setStatus('success');
const redirectTo = sessionStorage.getItem('oauth_redirect_to') || '/dashboard';
sessionStorage.removeItem('oauth_redirect_to');
navigate(redirectTo, { replace: true });
} else {
throw new Error('Authentication failed');
}
}
} catch (err) {
setStatus('error');
setError({
code: 'auth_failed',
description: err.message || 'Authentication failed'
});
}
};
if (status === 'processing') {
return (
<div className="oauth-callback-processing">
<div className="spinner"></div>
<h2>Completing authentication...</h2>
<p>Please wait while we finish setting up your account.</p>
</div>
);
}
if (status === 'success') {
return (
<div className="oauth-callback-success">
<div className="success-icon">✅</div>
<h2>Authentication successful!</h2>
<p>Redirecting you to your dashboard...</p>
</div>
);
}
if (status === 'error') {
return (
<div className="oauth-callback-error">
<div className="error-icon">❌</div>
<h2>Authentication failed</h2>
<p>{error.description}</p>
<div className="error-actions">
<button onClick={() => navigate('/login')}>
Try Again
</button>
<button onClick={() => navigate('/support')}>
Get Help
</button>
</div>
</div>
);
}
return null;
}
export default OAuthCallback;
OAuth Token Extraction Hook
import { useEffect, useState } from 'react';
function useOAuthTokens() {
const [tokens, setTokens] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
extractTokensFromCallback();
}, []);
const extractTokensFromCallback = () => {
try {
// Check URL parameters first
const urlParams = new URLSearchParams(window.location.search);
const accessToken = urlParams.get('access_token');
const refreshToken = urlParams.get('refresh_token');
const expiresIn = urlParams.get('expires_in');
if (accessToken) {
const tokenData = {
access_token: accessToken,
refresh_token: refreshToken,
expires_in: expiresIn ? parseInt(expiresIn) : null,
expires_at: expiresIn ? Date.now() + parseInt(expiresIn) * 1000 : null
};
setTokens(tokenData);
// Store tokens securely
localStorage.setItem('auth_tokens', JSON.stringify(tokenData));
// Clean URL
window.history.replaceState({}, document.title, window.location.pathname);
} else {
// Check if tokens are in localStorage
const storedTokens = localStorage.getItem('auth_tokens');
if (storedTokens) {
setTokens(JSON.parse(storedTokens));
}
}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const clearTokens = () => {
setTokens(null);
localStorage.removeItem('auth_tokens');
};
const isTokenExpired = () => {
if (!tokens?.expires_at) return false;
return Date.now() >= tokens.expires_at;
};
return {
tokens,
loading,
error,
clearTokens,
isTokenExpired
};
}
export default useOAuthTokens;
Node.js OAuth Callback Handler
const express = require('express');
const { query, validationResult } = require('express-validator');
const router = express.Router();
router.get('/callback', [
query('code').optional().isString(),
query('state').optional().isString(),
query('error').optional().isString(),
query('provider').optional().isString()
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
code: 400,
msg: 'Invalid callback parameters',
details: errors.array()
});
}
const { code, state, error, error_description, provider } = req.query;
// Handle OAuth errors
if (error) {
const errorUrl = `${process.env.APP_URL}/login?error=${error}&error_description=${encodeURIComponent(error_description || '')}`;
return res.redirect(errorUrl);
}
// Validate authorization code
if (!code) {
return res.status(400).json({
code: 400,
msg: 'Missing authorization code',
details: 'Authorization code is required for OAuth callback'
});
}
// Validate state parameter (if provided)
if (state) {
const storedState = req.session?.oauth_state;
if (!storedState || storedState !== state) {
return res.status(400).json({
code: 400,
msg: 'State parameter mismatch',
details: 'Invalid state parameter - possible CSRF attack'
});
}
}
// Forward to Strike Auth Service for token exchange
const callbackUrl = `${process.env.AUTH_SERVICE_URL}/callback?${req.url.split('?')[1]}`;
const response = await fetch(callbackUrl, {
method: 'GET',
headers: {
'User-Agent': req.headers['user-agent'],
'X-Forwarded-For': req.ip,
'X-Real-IP': req.ip
},
redirect: 'manual'
});
// Handle redirect response
if (response.status === 302) {
const location = response.headers.get('location');
// Extract and set cookies if present
const cookies = response.headers.get('set-cookie');
if (cookies) {
res.set('Set-Cookie', cookies);
}
return res.redirect(location);
}
// Handle error response
if (!response.ok) {
const errorData = await response.json();
return res.status(response.status).json(errorData);
}
// This shouldn't happen in normal flow
const data = await response.json();
res.json(data);
} catch (error) {
console.error('OAuth callback error:', error);
// Redirect to error page
const errorUrl = `${process.env.APP_URL}/login?error=server_error&error_description=${encodeURIComponent('Authentication failed')}`;
res.redirect(errorUrl);
}
});
module.exports = router;
OAuth Error Handler Component
import { useSearchParams, useNavigate } from 'react-router-dom';
function OAuthError() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const error = searchParams.get('error');
const errorDescription = searchParams.get('error_description');
const getErrorMessage = (errorCode) => {
const errorMessages = {
'access_denied': 'You denied access to your account. Please try again if you want to sign in.',
'invalid_request': 'The authentication request was invalid. Please try again.',
'unauthorized_client': 'This application is not authorized to use this authentication method.',
'unsupported_response_type': 'The authentication method is not supported.',
'invalid_scope': 'The requested permissions are invalid.',
'server_error': 'The authentication server encountered an error. Please try again.',
'temporarily_unavailable': 'The authentication service is temporarily unavailable. Please try again later.',
'state_mismatch': 'Security validation failed. Please try again.',
'auth_failed': 'Authentication failed. Please try again.'
};
return errorMessages[errorCode] || 'An unknown error occurred during authentication.';
};
const getErrorSolution = (errorCode) => {
const solutions = {
'access_denied': 'Click "Try Again" to restart the authentication process.',
'server_error': 'Wait a moment and try again. If the problem persists, contact support.',
'temporarily_unavailable': 'Please wait a few minutes and try again.',
'state_mismatch': 'Clear your browser cache and try again.',
'auth_failed': 'Check your internet connection and try again.'
};
return solutions[errorCode] || 'Please try again or contact support if the problem persists.';
};
return (
<div className="oauth-error-page">
<div className="error-container">
<div className="error-icon">⚠️</div>
<h1>Authentication Failed</h1>
<div className="error-details">
<p className="error-message">
{getErrorMessage(error)}
</p>
{errorDescription && (
<p className="error-description">
{decodeURIComponent(errorDescription)}
</p>
)}
<p className="error-solution">
{getErrorSolution(error)}
</p>
</div>
<div className="error-actions">
<button
onClick={() => navigate('/login')}
className="primary-button"
>
Try Again
</button>
<button
onClick={() => navigate('/signup')}
className="secondary-button"
>
Create Account
</button>
<button
onClick={() => navigate('/support')}
className="text-button"
>
Contact Support
</button>
</div>
<div className="error-code">
Error Code: {error || 'unknown'}
</div>
</div>
</div>
);
}
export default OAuthError;
Security Considerations
- State Parameter Validation: Always validate the state parameter to prevent CSRF attacks
- Authorization Code Expiration: Authorization codes are short-lived (typically 10 minutes)
- Token Security: Store tokens securely and use HttpOnly cookies when possible
- Redirect URL Validation: Ensure redirect URLs are whitelisted
- Error Handling: Don’t expose sensitive error information to users
Callback URL Configuration
Development
http://localhost:8080/callback
Production
https://yourapp.com/auth/callback
Multiple Environments
const getCallbackURL = () => {
const baseURL = process.env.NODE_ENV === 'production'
? 'https://yourapp.com'
: 'http://localhost:3000';
return `${baseURL}/auth/callback`;
};
Best Practices
Security
Security
- Always validate state parameters
- Use HTTPS in production
- Implement proper error handling
- Store tokens securely
- Set appropriate cookie flags
User Experience
User Experience
- Show loading states during processing
- Provide clear error messages
- Offer alternative authentication methods
- Handle network failures gracefully
- Clean URLs after token extraction
Error Handling
Error Handling
- Log OAuth errors for debugging
- Provide user-friendly error messages
- Implement retry mechanisms
- Handle expired authorization codes
- Gracefully handle provider downtime
Testing
Unit Tests
describe('GET /callback', () => {
test('should handle successful OAuth callback', async () => {
const response = await request(app)
.get('/callback')
.query({
code: 'valid_auth_code',
state: 'valid_state'
})
.expect(302);
expect(response.headers.location).toContain('/dashboard');
});
test('should handle OAuth error', async () => {
const response = await request(app)
.get('/callback')
.query({
error: 'access_denied',
error_description: 'User denied access'
})
.expect(302);
expect(response.headers.location).toContain('error=access_denied');
});
test('should reject missing authorization code', async () => {
await request(app)
.get('/callback')
.query({
state: 'valid_state'
})
.expect(400);
});
});
Related Endpoints
OAuth Authorize
Initiate OAuth authorization flow
Login
Traditional email/password login
Get Profile
Get authenticated user profile
Logout
End user sessions
⌘I