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"
Query Parameters
Authorization code returned by the OAuth provider.
State parameter for CSRF protection (if provided in the authorization request).
OAuth provider identifier (may be included in some implementations).
Error code if the OAuth authorization failed.
Human-readable error description if the OAuth authorization failed.
Response
This endpoint typically returns a 302 Found redirect response to the application’s redirect URL with authentication tokens.
Redirect URL with authentication tokens or session information
302 - Successful Authentication
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
302 - OAuth Error
400 - Invalid Request
400 - State Mismatch
500 - Provider Error
HTTP / 1.1 302 Found
Location : https://yourapp.com/login?error=access_denied&error_description=User%20denied%20access
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 ;
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
Always validate state parameters
Use HTTPS in production
Implement proper error handling
Store tokens securely
Set appropriate cookie flags
Show loading states during processing
Provide clear error messages
Offer alternative authentication methods
Handle network failures gracefully
Clean URLs after token extraction
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 );
});
});