IDP Support Center
  • Welcome
  • Getting started
    • What is IDP?
  • User Guide
    • User guide center
    • Get started with OXU
      • OXU Identity user guide
    • Account Management
      • Authentication
        • Sign up with Email and Password
        • Sign up with Google (optional)
        • Sign in with Email and Password
        • Google OIDC: Sign in to Google via OXU
        • Password requirements enforcement
        • Secure password hashing and storage
      • Account security
        • What should I do if I don't receive a verification code when signing up or forgot password?
        • Email verification
        • Two-Factor Authentication
          • Passkey authentication
          • 2FA-Email verification codes
          • MFA-Authenticator apps (TOTP)
          • MFA-Passkeys (FIDO2 / WebAuthn)
      • Data & Privacy
      • User Experience
        • Cross-platform web support
      • App Consent Management
    • Organization Admin App
      • Business Owner (Default Authority)
        • Organization Management
          • Organization creation
          • Domain ownership verification (DNS)
        • Organizational Structure
          • Organizational Units (OUs)
          • Workspaces
          • Hierarchical access scoping
        • Security & Governance
          • Enforced security policies
            • IP-Based access control
            • Geo-based access policies
            • Device and platform restrictions
          • Enforcing stronger authentication for risky login behavior
            • Risk Detection signals
            • Step-Up authentication
        • Roles & permissions
          • Assign role to user in Organization
          • Permissions list
      • SCIM – Automated user and workspace provisioning
        • What is SCIM used for?
        • User Lifecycle management
      • Administrator
        • Team & Access
          • Invite and manage users
          • Assign roles
          • Manage access at OU and Workspace level
            • Manage Access at Organizational Unit (OU)
            • Manage Workspace level
              • Team & Access – Members Management
        • Group management
        • Organization Unit
          • Create & manage Organization Units
          • Viewing and searching Organizational Units
          • Moving an Organizational Unit
          • Deleting an Organizational Unit
        • Manage activity logs of Organization's member
      • Become an OXU developer
      • App management - Workspace access
      • Report & Analytics Center
    • OXU Workspace
      • Guide to create workspace
      • Guide to manage workspace information
      • Guide to manage role and permissions
      • Applications
        • Guide to manage applications
        • OXU Developer
          • What is OXU developer
          • User guides
            • 1. Become OXU developer
            • 2. Create an app
            • 3. Input application info
              • About app ratings and reviews
            • 4. Config resource & security info
            • Security Best Practices
            • 5. Set up Pricing info
            • 6. Publish your app
              • Prepare before publishing your app
            • 7. Manage your app
              • App lifecycle
          • App versioning
        • OXU Store
          • What is OXU Store
          • 1. Register as an user
          • 2. Browsing & searching apps
          • 3. View app details
          • 4. Subscribe an app
            • Enable & Subscribe app for business workspace
          • 5. Manage subscriptions
          • 6. Rate & review an app
  • Support
    • Support center
      • What is Oten account & what can I do with Account Management App?
      • How to create account and password?
      • How to manage your Oten account information?
      • How to use MFA to protect your account?
      • What is WorkSpace & what can you do with WS?
      • What is Organization Admin app & what can you do with OAA?
    • Privacy Policy
    • Terms and conditions
      • Oten developer terms and conditions
      • Oten Store terms and conditions
    • FAQs
      • Store FAQs
      • Developer FAQ
    • Contact Us
  • Integration
    • Integration document
      • IDP integration
        • Environments: sandbox & production
        • Regular web application client
        • Native application client
        • Single page application client
        • SAML integration
        • Managing your integration applications
        • FAQ
      • What is SSO?
      • Why use SSO?
    • Provisioning connector
      • Google Workspace Configuration
    • Understand SSO flow
      • Overview
      • Flow Diagram
    • Developer Integration guide
      • Integration flow overview
    • Oten to OXU Migration guide
    • Prerequisites
      • Discovery Configuration
      • JAR Requirement - CRITICAL
      • JAR Complete Implementation Guide
      • PKCE Implementation Guide
        • Step 1: Choose OAuth Library
        • Step 2: Configure OAuth Client
        • Step 3: Implement Authorization Flow
        • Step 4: Handle Callback
        • Step 5: Token Management
      • Best practice
        • Security
      • Support & Troubleshoot
        • Common Errors
        • Debug and Troubleshooting
        • Contact Support
      • Appendix
        • Configuration Reference
        • Error Codes Reference
        • API Reference
        • Sample Code
        • Glossary
  • What's New
    • v1.0.29 - Aug 19, 2026
    • v1.0.28 - Aug 12, 2026
    • v1.0.27 - Aug 08, 2026
    • v1.0.26 - July 29 & 31, 2026
    • v1.0.25 - July 22, 2026
    • v1.0.24 - Jun 21, 2026
    • v1.0.23 - Jun 17, 2026
    • v1.0.22 - Jun 03, 2026
    • v1.0.21 - May 27, 2026
    • v1.0.20 - Apr 28, 2026
    • v1.0.19 - Apr 21, 2026
    • v1.0.18 - Apr 15, 2026
    • v1.0.17 - Apr 03, 2026
    • v1.0.16 - Mar 28, 2026
    • v1.0.15 - Mar 05 & 13, 2026
    • v1.0.14 - Feb 11, 2026
    • v1.0.13 - Jan 14, 2026
    • v1.0.12 - Jan 05, 2026
    • v1.0.11 - Jan 04, 2026
    • v1.0.10 - Dec 25, 2025
    • v1.0.9 - Dec 07, 2025
    • v1.0.8 - Nov 23, 2025
    • v1.0.7 - Nov 09, 2025
    • v1.0.6 - Oct 26, 2025
    • v1.0.5 - Sep 29, 2025
    • v1.0.4 - Sep 28, 2025
    • v1.0.3 - Sep 14, 2025
    • v1.0.2 - Aug 31, 2025
    • v1.0.1 - Aug 17, 2025
    • v1.0.0 - Aug 03, 2025
On this page
  1. Integration
  2. Prerequisites
  3. PKCE Implementation Guide

Step 4: Handle Callback

PreviousStep 3: Implement Authorization FlowNextStep 5: Token Management

Last updated 9 months ago

  • 🎯 What You'll Learn
  • 🔄 Callback Flow Overview
  • 🔍 Callback Request Structure
  • Successful Callback
  • Error Callback
  • 🔐 Security Validation
  • State Parameter Validation
  • Authorization Code Validation
  • 🔄 Token Exchange
  • Basic Token Exchange
  • Token Response Structure
  • Token Validation
  • 🌐 Implementation Examples
  • Express.js Callback Handler
  • React SPA Callback Handler
  • Python Flask Callback Handler
  • 🚨 Error Handling
  • Authorization Callback Error Scenarios
  • Token Exchange Error Handling
  • Logging and Monitoring
  • Authentication Event Logging
  • Callback Handler Checklist
  • Navigation

After users authenticate with Oten IDP, they're redirected back to your application with an authorization code. This step shows you how to handle the callback, validate parameters, and exchange the code for tokens.

📖 Need context? Check the Integration Flow Overview to see how this step fits into the complete process.

🎯 What You'll Learn

In this step, you will:

  • Handle the OAuth callback request

  • Validate state parameter and other security checks

  • Exchange authorization code for tokens

  • Handle errors and edge cases

  • Implement proper error handling and logging

🔄 Callback Flow Overview

When authentication succeeds, Oten redirects to your callback URL with:

Parameters:

  • code: Authorization code (10-minute expiry)

  • state: The state value you sent (for CSRF protection)

When authentication fails, the callback includes error information:

Error Parameters:

📖 Complete Error Reference: See Error Codes Reference for all error codes and handling guidelines.

Authorization errors are returned as URL parameters in the callback. Handle these according to OAuth 2.0 standards:

Token exchange errors are returned as JSON responses. Handle these according to OAuth 2.0 token endpoint specifications:

Ensure your callback handler:


Progress: Step 4 of 5 complete ✅

🔍 Callback Request Structure

Successful Callback

https://yourapp.com/callback?code=AUTH_CODE&state=STATE_VALUE

Error Callback

https://yourapp.com/callback?error=access_denied&error_description=User+denied+access&state=STATE_VALUE
  • error: Error code (e.g., access_denied, invalid_request)

  • error_description: Human-readable error description

  • state: The state value (still present for validation)

🔐 Security Validation

State Parameter Validation

function validateState(receivedState, req) {
  const storedState = req.session.oauthState;
  
  // Clear stored state (one-time use)
  delete req.session.oauthState;
  
  if (!receivedState) {
    throw new Error('Missing state parameter');
  }
  
  if (!storedState) {
    throw new Error('No stored state found - possible session timeout');
  }
  
  if (receivedState !== storedState) {
    throw new Error('Invalid state parameter - possible CSRF attack');
  }
  
  return true;
}

Authorization Code Validation

function validateAuthorizationCode(code) {
  if (!code) {
    throw new Error('Missing authorization code');
  }
  
  if (typeof code !== 'string') {
    throw new Error('Invalid authorization code format');
  }
  
  // Basic length validation (codes are typically 20-512 characters)
  if (code.length < 10 || code.length > 512) {
    throw new Error('Authorization code length invalid');
  }
  
  // Check for valid characters (base64url)
  if (!/^[A-Za-z0-9_-]+$/.test(code)) {
    throw new Error('Authorization code contains invalid characters');
  }
  
  return true;
}

🔄 Token Exchange

Basic Token Exchange

async function exchangeCodeForTokens(code, codeVerifier = null) {
  const tokenRequest = {
    grant_type: 'authorization_code',
    code: code,
    redirect_uri: process.env.OTEN_REDIRECT_URI,
    client_id: process.env.OTEN_CLIENT_ID
  };
  
  // Add client secret for confidential clients
  if (process.env.OTEN_CLIENT_SECRET) {
    tokenRequest.client_secret = process.env.OTEN_CLIENT_SECRET;
  }
  
  // Add PKCE code verifier for public clients
  if (codeVerifier) {
    tokenRequest.code_verifier = codeVerifier;
  }
  
  try {
    const response = await fetch('https://account.oten.com/v1/oauth/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'application/json'
      },
      body: new URLSearchParams(tokenRequest)
    });
    
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`Token exchange failed: ${errorData.error_description || errorData.error}`);
    }
    
    const tokens = await response.json();
    return tokens;
    
  } catch (error) {
    console.error('Token exchange error:', error);
    throw error;
  }
}

Token Response Structure

// Successful token response
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200...",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "openid profile email"
}

Token Validation

const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

// Create JWKS client for token validation
const client = jwksClient({
  jwksUri: 'https://account.oten.com/.well-known/jwks.json',
  cache: true,
  cacheMaxAge: 86400000, // 24 hours
  rateLimit: true,
  jwksRequestsPerMinute: 5
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) {
      callback(err);
      return;
    }
    
    const signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

async function validateIDToken(idToken) {
  return new Promise((resolve, reject) => {
    jwt.verify(idToken, getKey, {
      issuer: 'https://account.oten.com',
      audience: process.env.OTEN_CLIENT_ID,
      algorithms: ['RS256']
    }, (err, decoded) => {
      if (err) {
        reject(new Error(`Invalid ID token: ${err.message}`));
        return;
      }
      
      // Additional validations
      const now = Math.floor(Date.now() / 1000);
      
      if (decoded.exp <= now) {
        reject(new Error('ID token has expired'));
        return;
      }
      
      if (decoded.iat > now + 300) { // Allow 5 minutes clock skew
        reject(new Error('ID token issued in the future'));
        return;
      }
      
      resolve(decoded);
    });
  });
}

🌐 Implementation Examples

Express.js Callback Handler

app.get('/callback', async (req, res) => {
  try {
    const { code, state, error, error_description } = req.query;
    
    // Handle OAuth errors
    if (error) {
      console.error('OAuth error:', error, error_description);
      return res.redirect('/login?error=' + encodeURIComponent(error_description || error));
    }
    
    // Validate state parameter
    validateState(state, req);
    
    // Validate authorization code
    validateAuthorizationCode(code);
    
    // Get code verifier from session (if using PKCE)
    const codeVerifier = req.session.codeVerifier;
    delete req.session.codeVerifier; // Clear after use
    
    // Exchange code for tokens
    const tokens = await exchangeCodeForTokens(code, codeVerifier);
    
    // Validate ID token
    const idTokenClaims = await validateIDToken(tokens.id_token);
    
    // Store tokens and user information
    await storeUserSession(req, tokens, idTokenClaims);
    
    // Redirect to application
    res.redirect('/dashboard');
    
  } catch (error) {
    console.error('Callback error:', error);
    res.redirect('/login?error=' + encodeURIComponent('Authentication failed'));
  }
});

async function storeUserSession(req, tokens, userClaims) {
  // Store user information in session
  req.session.user = {
    id: userClaims.sub,
    email: userClaims.email,
    name: userClaims.name,
    workspace: userClaims.workspace
  };
  
  // Store tokens securely (encrypted)
  req.session.tokens = {
    accessToken: encrypt(tokens.access_token),
    refreshToken: encrypt(tokens.refresh_token),
    idToken: encrypt(tokens.id_token),
    expiresAt: new Date(Date.now() + tokens.expires_in * 1000)
  };
  
  // Log successful authentication
  console.log('User authenticated:', userClaims.sub, userClaims.email);
}

React SPA Callback Handler

// CallbackPage.jsx
import React, { useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';

function CallbackPage() {
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  const [status, setStatus] = useState('processing');
  const [error, setError] = useState(null);
  
  useEffect(() => {
    handleCallback();
  }, []);
  
  async function handleCallback() {
    try {
      const code = searchParams.get('code');
      const state = searchParams.get('state');
      const error = searchParams.get('error');
      const errorDescription = searchParams.get('error_description');
      
      if (error) {
        throw new Error(errorDescription || error);
      }
      
      if (!code) {
        throw new Error('No authorization code received');
      }
      
      // Validate state
      const storedState = sessionStorage.getItem('oauth_state');
      sessionStorage.removeItem('oauth_state');
      
      if (state !== storedState) {
        throw new Error('Invalid state parameter');
      }
      
      // Exchange code for tokens
      const codeVerifier = sessionStorage.getItem('code_verifier');
      sessionStorage.removeItem('code_verifier');
      
      const tokens = await exchangeCodeForTokens(code, codeVerifier);
      
      // Store tokens
      localStorage.setItem('access_token', tokens.access_token);
      localStorage.setItem('refresh_token', tokens.refresh_token);
      localStorage.setItem('id_token', tokens.id_token);
      
      // Parse user info from ID token
      const userInfo = parseJWT(tokens.id_token);
      localStorage.setItem('user_info', JSON.stringify(userInfo));
      
      setStatus('success');
      
      // Redirect to app
      setTimeout(() => {
        navigate('/dashboard');
      }, 1000);
      
    } catch (error) {
      console.error('Callback error:', error);
      setError(error.message);
      setStatus('error');
    }
  }
  
  if (status === 'processing') {
    return <div>Processing login...</div>;
  }
  
  if (status === 'error') {
    return (
      <div>
        <h2>Login Failed</h2>
        <p>{error}</p>
        <button onClick={() => navigate('/login')}>Try Again</button>
      </div>
    );
  }
  
  return <div>Login successful! Redirecting...</div>;
}

function parseJWT(token) {
  const base64Url = token.split('.')[1];
  const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
    return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
  }).join(''));
  
  return JSON.parse(jsonPayload);
}

Python Flask Callback Handler

@app.route('/callback')
def callback():
    try:
        # Check for OAuth errors
        error = request.args.get('error')
        if error:
            error_description = request.args.get('error_description', error)
            flash(f'Authentication failed: {error_description}')
            return redirect(url_for('login'))
        
        # Get authorization code
        code = request.args.get('code')
        if not code:
            flash('No authorization code received')
            return redirect(url_for('login'))
        
        # Validate state parameter
        received_state = request.args.get('state')
        stored_state = session.pop('oauth_state', None)
        
        if not received_state or received_state != stored_state:
            flash('Invalid state parameter')
            return redirect(url_for('login'))
        
        # Exchange code for tokens
        code_verifier = session.pop('code_verifier', None)
        token = oten.authorize_access_token(
            code_verifier=code_verifier
        )
        
        # Parse user info from ID token
        user_info = oten.parse_id_token(token)
        
        # Store user session
        session['user'] = {
            'id': user_info['sub'],
            'email': user_info['email'],
            'name': user_info['name']
        }
        
        # Store tokens (encrypted)
        session['tokens'] = encrypt_tokens(token)
        
        flash('Login successful!')
        return redirect(url_for('dashboard'))
        
    except Exception as e:
        app.logger.error(f'Callback error: {str(e)}')
        flash('Authentication failed')
        return redirect(url_for('login'))

🚨 Error Handling

Authorization Callback Error Scenarios

function handleAuthorizationCallbackError(error, errorDescription, state, req, res) {
  // Validate state parameter first
  const storedState = req.session.oauthState;
  if (state !== storedState) {
    console.error('State parameter mismatch - possible CSRF attack');
    return res.redirect('/login?error=security_error');
  }

  const errorMap = {
    // Standard OAuth 2.0 errors
    'access_denied': {
      message: 'You denied access to the application',
      action: 'Please try again and grant access to continue',
      redirect: '/login',
      severity: 'info'
    },
    'invalid_request': {
      message: 'Invalid authentication request',
      action: 'Please contact support if this persists',
      redirect: '/login',
      severity: 'error'
    },
    'invalid_client': {
      message: 'Application configuration error',
      action: 'Please contact support',
      redirect: '/error',
      severity: 'error'
    },
    'unauthorized_client': {
      message: 'Application not authorized',
      action: 'Please contact your administrator',
      redirect: '/error',
      severity: 'error'
    },
    'unsupported_response_type': {
      message: 'Authentication method not supported',
      action: 'Please contact support',
      redirect: '/error',
      severity: 'error'
    },
    'invalid_scope': {
      message: 'Requested permissions not available',
      action: 'Please contact support',
      redirect: '/error',
      severity: 'error'
    },
    'server_error': {
      message: 'Authentication service temporarily unavailable',
      action: 'Please try again in a few minutes',
      redirect: '/login',
      severity: 'warning'
    },
    'temporarily_unavailable': {
      message: 'Authentication service is busy',
      action: 'Please wait a moment and try again',
      redirect: '/login',
      severity: 'warning'
    },

    // JAR-specific errors
    'invalid_request_object': {
      message: 'Authentication request format error',
      action: 'Please contact support',
      redirect: '/error',
      severity: 'error'
    },
    'request_not_supported': {
      message: 'Authentication method not supported',
      action: 'Please contact support',
      redirect: '/error',
      severity: 'error'
    },

    // Oten specific scenarios (using standard error codes)
    'invalid_request': {
      message: 'Authentication request error',
      action: 'Please try logging in again or contact support',
      redirect: '/login',
      severity: 'warning'
    },
    'workspace_required': {
      message: 'Workspace selection required',
      action: 'Please select a workspace and try again',
      redirect: '/workspace-selection',
      severity: 'info'
    },
    'mfa_required': {
      message: 'Multi-factor authentication required',
      action: 'Please complete additional authentication steps',
      redirect: '/mfa',
      severity: 'info'
    }
  };

  const errorInfo = errorMap[error] || {
    message: 'Authentication failed',
    action: 'Please try again',
    redirect: '/login',
    severity: 'error'
  };

  // Log error for debugging
  console.error('OAuth authorization callback error:', {
    error,
    errorDescription,
    state,
    severity: errorInfo.severity,
    userAgent: req.headers['user-agent'],
    ip: req.ip,
    timestamp: new Date().toISOString()
  });

  // Clear OAuth session data
  delete req.session.oauthState;
  delete req.session.codeVerifier;

  // Redirect with user-friendly error
  const errorQuery = new URLSearchParams({
    error: errorInfo.message,
    action: errorInfo.action,
    severity: errorInfo.severity
  });

  res.redirect(`${errorInfo.redirect}?${errorQuery}`);
}

Token Exchange Error Handling

async function handleTokenExchangeError(error, response) {
  // Parse error response
  let errorData;
  try {
    errorData = await response.json();
  } catch (parseError) {
    errorData = {
      error: 'server_error',
      error_description: 'Unable to parse error response'
    };
  }

  const { error: errorCode, error_description: errorDescription } = errorData;

  // Map token errors to user actions
  const tokenErrorMap = {
    // Standard OAuth 2.0 token errors
    'invalid_request': {
      message: 'Invalid token request',
      action: 'restart_auth',
      retryable: false,
      severity: 'error'
    },
    'invalid_client': {
      message: 'Client authentication failed',
      action: 'contact_support',
      retryable: false,
      severity: 'error'
    },
    'invalid_grant': {
      message: 'Authorization code invalid or expired',
      action: 'restart_auth',
      retryable: false,
      severity: 'warning'
    },
    'unauthorized_client': {
      message: 'Client not authorized for this grant type',
      action: 'contact_support',
      retryable: false,
      severity: 'error'
    },
    'unsupported_grant_type': {
      message: 'Grant type not supported',
      action: 'contact_support',
      retryable: false,
      severity: 'error'
    },
    'invalid_scope': {
      message: 'Requested scope invalid',
      action: 'restart_auth',
      retryable: false,
      severity: 'warning'
    },
    'server_error': {
      message: 'Token service temporarily unavailable',
      action: 'retry',
      retryable: true,
      severity: 'warning'
    }
  };

  const errorInfo = tokenErrorMap[errorCode] || {
    message: 'Token exchange failed',
    action: 'restart_auth',
    retryable: false,
    severity: 'error'
  };

  // Log detailed error information
  console.error('Token exchange error:', {
    errorCode,
    errorDescription,
    httpStatus: response.status,
    action: errorInfo.action,
    retryable: errorInfo.retryable,
    severity: errorInfo.severity,
    timestamp: new Date().toISOString()
  });

  return {
    ...errorInfo,
    errorCode,
    errorDescription,
    httpStatus: response.status
  };
}

async function exchangeCodeForTokensWithRetry(code, codeVerifier, maxRetries = 3) {
  let lastError;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('/oauth/token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: new URLSearchParams({
          grant_type: 'authorization_code',
          code: code,
          redirect_uri: process.env.REDIRECT_URI,
          client_id: process.env.CLIENT_ID,
          client_secret: process.env.CLIENT_SECRET,
          code_verifier: codeVerifier
        })
      });

      if (response.ok) {
        return await response.json();
      }

      // Handle token exchange error
      const errorInfo = await handleTokenExchangeError(null, response);

      // Don't retry for non-retryable errors
      if (!errorInfo.retryable) {
        throw new Error(`Token exchange failed: ${errorInfo.message}`);
      }

      lastError = new Error(`Token exchange failed (attempt ${attempt}): ${errorInfo.message}`);

      // Exponential backoff for retryable errors
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
        await new Promise(resolve => setTimeout(resolve, delay));
      }

    } catch (error) {
      lastError = error;
      
      // Don't retry on client errors (4xx)
      if (error.message.includes('400') || error.message.includes('401')) {
        throw error;
      }
      
      // Retry on server errors (5xx) with exponential backoff
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
        await new Promise(resolve => setTimeout(resolve, delay));
        console.log(`Token exchange attempt ${attempt} failed, retrying in ${delay}ms...`);
      }
    }
  }
  
  throw lastError;
}

Logging and Monitoring

Authentication Event Logging

function logAuthenticationEvent(event, details) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    event: event,
    details: details,
    source: 'oauth-callback'
  };
  
  // Send to logging service
  console.log('Auth event:', JSON.stringify(logEntry));
  
  // Send metrics to monitoring service
  if (event === 'callback_success') {
    metrics.increment('auth.callback.success');
  } else if (event === 'callback_error') {
    metrics.increment('auth.callback.error', { error_type: details.error });
  }
}

// Usage in callback handler
logAuthenticationEvent('callback_success', {
  userId: userClaims.sub,
  email: userClaims.email,
  workspace: userClaims.workspace
});

Callback Handler Checklist

Navigation

  • ← Previous: Step 3: Authorization Flow - Implement JAR authorization

    ↑ Overview: Integration Flow Overview - See the big picture

    → Next: Step 5: Token Management - Secure token storage and refresh