Skip to main content

Setup OpenID Connect (OIDC)

Configure OpenID Connect for modern, secure authentication in your AI applications and MCP servers.

Overview

OpenID Connect (OIDC) is an identity layer built on top of OAuth 2.0. It provides a standardized way to authenticate users and obtain their identity information. OIDC is ideal for AI applications that need secure user authentication with modern web standards.

Benefits of OIDC

  • Standardized Protocol: Industry-standard authentication
  • Secure by Default: Built-in security best practices
  • Rich User Information: Access to user profile and claims
  • Token-Based: Stateless authentication with JWT tokens
  • Federation Support: Connect with multiple identity providers

OIDC Configuration

1. Enable OIDC in Authsec

Configure OIDC settings in your Authsec dashboard:

  1. Navigate to ApplicationsYour AI App
  2. Go to the Authentication tab
  3. Enable OpenID Connect
  4. Configure the following settings:
{
"oidc_conformant": true,
"token_endpoint_auth_method": "client_secret_post",
"response_types": ["code", "id_token", "token"],
"grant_types": ["authorization_code", "refresh_token", "client_credentials"],
"id_token_signed_response_alg": "RS256"
}

2. Configure Scopes

Define the information your application needs:

{
"allowed_scopes": [
"openid", // Required for OIDC
"profile", // User profile information
"email", // User email address
"ai:access", // Custom AI access scope
"mcp:tools", // MCP tool access
"offline_access" // Refresh token capability
]
}

3. Set Redirect URIs

Configure allowed callback URLs:

{
"redirect_uris": [
"https://yourapp.com/auth/callback",
"https://yourapp.com/silent-auth",
"http://localhost:3000/callback" // For development
],
"post_logout_redirect_uris": [
"https://yourapp.com/logout",
"https://yourapp.com/"
]
}

Implementation

Web Application Flow

1. Authorization Request

import { AuthsecClient } from '@authsec/sdk';

const authsec = new AuthsecClient({
domain: 'your-domain.authsec.com',
clientId: 'your-client-id',
redirectUri: 'https://yourapp.com/callback'
});

// Redirect user to authorization server
function login() {
const authUrl = authsec.buildAuthorizeUrl({
response_type: 'code',
scope: 'openid profile email ai:access',
state: generateRandomState(), // CSRF protection
nonce: generateRandomNonce() // Replay attack protection
});

window.location.href = authUrl;
}

2. Handle Authorization Response

// Handle the callback from Authsec
async function handleCallback(req, res) {
try {
const { code, state } = req.query;

// Verify state parameter (CSRF protection)
if (state !== getStoredState()) {
throw new Error('Invalid state parameter');
}

// Exchange authorization code for tokens
const tokens = await authsec.exchangeCodeForTokens({
code: code,
redirect_uri: 'https://yourapp.com/callback'
});

// Validate and decode ID token
const idToken = await authsec.verifyIdToken(tokens.id_token);

// Store tokens securely (server-side session or secure cookies)
req.session.accessToken = tokens.access_token;
req.session.refreshToken = tokens.refresh_token;
req.session.user = idToken;

res.redirect('/dashboard');
} catch (error) {
console.error('OIDC callback error:', error);
res.redirect('/login?error=auth_failed');
}
}

Single Page Application (SPA) Flow

1. Initialize OIDC Client

import { AuthsecWebClient } from '@authsec/web-sdk';

const authsec = new AuthsecWebClient({
domain: 'your-domain.authsec.com',
clientId: 'your-spa-client-id',
redirectUri: window.location.origin + '/callback',
responseType: 'code',
scope: 'openid profile email ai:access',
usePKCE: true // Enable PKCE for security
});

2. Login Flow

// Login with redirect
async function login() {
try {
await authsec.loginWithRedirect();
} catch (error) {
console.error('Login failed:', error);
}
}

// Handle redirect callback
async function handleRedirectCallback() {
try {
const result = await authsec.handleRedirectCallback();
console.log('Login successful:', result.user);

// User is now authenticated
showDashboard();
} catch (error) {
console.error('Callback handling failed:', error);
showLoginError();
}
}

3. Silent Authentication

// Check if user is already authenticated
async function checkAuth() {
try {
const user = await authsec.getUser();
if (user) {
console.log('User is authenticated:', user);
return user;
} else {
// Try silent authentication
const silentUser = await authsec.getTokenSilently();
return silentUser;
}
} catch (error) {
console.log('User not authenticated');
return null;
}
}

Server-to-Server Flow

For AI agents and MCP servers that operate without user interaction:

// Client credentials flow for service authentication
async function authenticateService() {
try {
const tokenResponse = await authsec.clientCredentialsGrant({
scope: 'ai:generate ai:analyze mcp:tools',
audience: 'https://api.yourapp.com'
});

return tokenResponse.access_token;
} catch (error) {
console.error('Service authentication failed:', error);
throw error;
}
}

Token Validation

Validate Access Tokens

// Middleware to validate access tokens
async function validateAccessToken(req, res, next) {
try {
const token = extractTokenFromHeader(req.headers.authorization);

if (!token) {
return res.status(401).json({ error: 'No token provided' });
}

// Validate token with Authsec
const validationResult = await authsec.validateAccessToken(token, {
audience: 'https://api.yourapp.com',
issuer: `https://${process.env.AUTHSEC_DOMAIN}/`
});

if (validationResult.valid) {
req.user = validationResult.claims;
next();
} else {
return res.status(401).json({ error: 'Invalid token' });
}
} catch (error) {
console.error('Token validation error:', error);
return res.status(500).json({ error: 'Authentication error' });
}
}

Validate ID Tokens

// Validate ID tokens (for user authentication)
async function validateIdToken(idToken) {
try {
const decoded = await authsec.verifyIdToken(idToken, {
issuer: `https://${process.env.AUTHSEC_DOMAIN}/`,
audience: process.env.AUTHSEC_CLIENT_ID,
clockTolerance: 60 // Allow 60 seconds clock skew
});

// Verify custom claims if needed
if (decoded.nonce && decoded.nonce !== getStoredNonce()) {
throw new Error('Invalid nonce');
}

return decoded;
} catch (error) {
console.error('ID token validation failed:', error);
throw error;
}
}

User Information

Get User Profile

// Get user information from userinfo endpoint
async function getUserInfo(accessToken) {
try {
const userInfo = await authsec.getUserInfo(accessToken);

return {
id: userInfo.sub,
name: userInfo.name,
email: userInfo.email,
picture: userInfo.picture,
customClaims: {
aiAccess: userInfo['ai:access'],
mcpTools: userInfo['mcp:tools']
}
};
} catch (error) {
console.error('Failed to get user info:', error);
throw error;
}
}

Custom Claims

Add custom claims to ID tokens:

// Configure custom claims in Authsec dashboard or via API
const customClaims = {
"ai_permissions": {
"models": ["gpt-4", "claude-3"],
"rate_limit": 1000,
"features": ["generate", "analyze", "summarize"]
},
"mcp_access": {
"tools": ["web_search", "calculator", "file_access"],
"resources": ["documents", "images"],
"context_retention": "24h"
}
};

Logout

Logout Implementation

// Logout user and revoke tokens
async function logout() {
try {
// Get current tokens
const accessToken = getAccessToken();
const refreshToken = getRefreshToken();

// Revoke refresh token
if (refreshToken) {
await authsec.revokeToken(refreshToken);
}

// Clear local session
clearTokens();

// Redirect to Authsec logout URL
const logoutUrl = authsec.buildLogoutUrl({
returnTo: 'https://yourapp.com/',
client_id: process.env.AUTHSEC_CLIENT_ID
});

window.location.href = logoutUrl;
} catch (error) {
console.error('Logout error:', error);
// Clear local session anyway
clearTokens();
window.location.href = '/';
}
}

Security Considerations

PKCE (Proof Key for Code Exchange)

Always use PKCE for public clients:

// PKCE is automatically enabled for SPAs
const authsec = new AuthsecWebClient({
domain: 'your-domain.authsec.com',
clientId: 'your-spa-client-id',
usePKCE: true // Enabled by default
});

State Parameter

Use state parameter to prevent CSRF attacks:

function generateAndStoreState() {
const state = generateRandomString(32);
sessionStorage.setItem('auth_state', state);
return state;
}

function verifyState(receivedState) {
const storedState = sessionStorage.getItem('auth_state');
sessionStorage.removeItem('auth_state');
return storedState === receivedState;
}

Nonce Parameter

Use nonce to prevent replay attacks:

function generateAndStoreNonce() {
const nonce = generateRandomString(32);
sessionStorage.setItem('auth_nonce', nonce);
return nonce;
}

function verifyNonce(idToken) {
const storedNonce = sessionStorage.getItem('auth_nonce');
sessionStorage.removeItem('auth_nonce');

const decoded = jwt.decode(idToken);
return decoded.nonce === storedNonce;
}

Testing OIDC Integration

Unit Tests

// Test OIDC configuration
describe('OIDC Configuration', () => {
test('should build correct authorization URL', () => {
const authUrl = authsec.buildAuthorizeUrl({
response_type: 'code',
scope: 'openid profile',
state: 'test-state',
nonce: 'test-nonce'
});

expect(authUrl).toContain('response_type=code');
expect(authUrl).toContain('scope=openid%20profile');
expect(authUrl).toContain('state=test-state');
});

test('should validate ID tokens correctly', async () => {
const mockIdToken = 'eyJ...'; // Mock JWT
const result = await authsec.verifyIdToken(mockIdToken);

expect(result).toHaveProperty('sub');
expect(result).toHaveProperty('aud');
expect(result).toHaveProperty('exp');
});
});

Integration Tests

// Test full OIDC flow
describe('OIDC Flow Integration', () => {
test('should complete authorization code flow', async () => {
// Simulate authorization code callback
const mockCode = 'test-auth-code';
const tokens = await authsec.exchangeCodeForTokens({
code: mockCode,
redirect_uri: 'http://localhost:3000/callback'
});

expect(tokens).toHaveProperty('access_token');
expect(tokens).toHaveProperty('id_token');
expect(tokens).toHaveProperty('token_type', 'Bearer');
});
});

Troubleshooting

Common Issues

  1. Invalid Redirect URI: Ensure callback URLs match exactly
  2. Scope Not Granted: Check if requested scopes are configured
  3. Token Expired: Implement proper token refresh logic
  4. Nonce Mismatch: Verify nonce handling in ID token validation
  5. CORS Errors: Configure allowed origins in Authsec dashboard

Debug Tips

  1. Enable Debug Logging: Set debug: true in SDK configuration
  2. Check Network Tab: Inspect HTTP requests and responses
  3. Validate JWT Tokens: Use jwt.io to decode and inspect tokens
  4. Verify Configuration: Double-check client settings in Authsec dashboard
  5. Test with Postman: Use Postman to test API endpoints directly

Getting Help

  • Check the OIDC specification
  • Review Authsec dashboard logs
  • Contact support with specific error messages
  • Use browser developer tools to debug client-side issues