What is OpenID Connect (OIDC)?
OpenID Connect is an identity layer built on top of OAuth 2.0. It provides a simple way to verify the identity of users based on authentication performed by an authorization server, as well as to obtain basic profile information about the user.
OIDC vs OAuth 2.0
While OAuth 2.0 is primarily about authorization (what you can do), OIDC adds authentication (who you are) on top of it:
- OAuth 2.0: "Can this app access my data?"
- OIDC: "Who is this user and can this app access their data?"
OIDC Authentication Flows
1. Authorization Code Flow (Recommended)
The most secure flow for web applications:
// Step 1: Redirect to authorization server
const authUrl = `${oidcConfig.authorization_endpoint}?
client_id=${clientId}&
response_type=code&
scope=openid profile email&
redirect_uri=${redirectUri}&
state=${state}&
nonce=${nonce}`;
window.location.href = authUrl;
2. Implicit Flow (Deprecated)
Less secure, mainly for single-page applications (not recommended for new implementations):
// Implicit flow example (avoid in new projects)
const authUrl = `${oidcConfig.authorization_endpoint}?
client_id=${clientId}&
response_type=id_token token&
scope=openid profile email&
redirect_uri=${redirectUri}&
state=${state}&
nonce=${nonce}`;
Complete OIDC Implementation
1. Discovery Configuration
First, discover the OIDC provider's configuration:
// Discover OIDC configuration
async function discoverOIDCConfig(issuer) {
const discoveryUrl = `${issuer}/.well-known/openid_configuration`;
const response = await fetch(discoveryUrl);
return await response.json();
}
// Example usage
const oidcConfig = await discoverOIDCConfig('https://accounts.google.com');
console.log(oidcConfig);
2. Client Registration
Register your application with the OIDC provider:
// Client configuration
const clientConfig = {
client_id: 'your-client-id',
client_secret: 'your-client-secret', // For confidential clients
redirect_uris: ['https://yourapp.com/callback'],
response_types: ['code'],
grant_types: ['authorization_code', 'refresh_token'],
scope: 'openid profile email',
token_endpoint_auth_method: 'client_secret_post'
};
3. Authorization Request
Initiate the authentication flow:
function initiateAuth() {
const state = generateRandomString(32);
const nonce = generateRandomString(32);
// Store state and nonce for validation
sessionStorage.setItem('oidc_state', state);
sessionStorage.setItem('oidc_nonce', nonce);
const params = new URLSearchParams({
client_id: clientConfig.client_id,
response_type: 'code',
scope: 'openid profile email',
redirect_uri: clientConfig.redirect_uris[0],
state: state,
nonce: nonce,
response_mode: 'query'
});
const authUrl = `${oidcConfig.authorization_endpoint}?${params}`;
window.location.href = authUrl;
}
4. Token Exchange
Exchange authorization code for tokens:
async function exchangeCodeForTokens(code, state) {
// Validate state parameter
const storedState = sessionStorage.getItem('oidc_state');
if (state !== storedState) {
throw new Error('Invalid state parameter');
}
const tokenResponse = await fetch(oidcConfig.token_endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: clientConfig.redirect_uris[0],
client_id: clientConfig.client_id,
client_secret: clientConfig.client_secret
})
});
const tokens = await tokenResponse.json();
return tokens;
}
5. ID Token Validation
Validate the received ID token:
async function validateIdToken(idToken, nonce) {
// Decode JWT header and payload
const [header, payload, signature] = idToken.split('.');
const decodedPayload = JSON.parse(atob(payload));
// Validate claims
const now = Math.floor(Date.now() / 1000);
// Check expiration
if (decodedPayload.exp < now) {
throw new Error('ID token expired');
}
// Check issued at
if (decodedPayload.iat > now) {
throw new Error('ID token issued in the future');
}
// Check nonce
if (decodedPayload.nonce !== nonce) {
throw new Error('Invalid nonce');
}
// Check audience
if (decodedPayload.aud !== clientConfig.client_id) {
throw new Error('Invalid audience');
}
// Verify signature (simplified - use proper JWT library)
// In production, use a JWT library like jsonwebtoken or jose
return decodedPayload;
}
Popular OIDC Providers
Google OIDC
const googleOIDC = {
issuer: 'https://accounts.google.com',
client_id: 'your-google-client-id',
client_secret: 'your-google-client-secret',
redirect_uri: 'https://yourapp.com/callback',
scope: 'openid profile email'
};
Auth0 OIDC
const auth0OIDC = {
issuer: 'https://your-domain.auth0.com',
client_id: 'your-auth0-client-id',
client_secret: 'your-auth0-client-secret',
redirect_uri: 'https://yourapp.com/callback',
scope: 'openid profile email'
};
Microsoft Azure AD
const azureOIDC = {
issuer: 'https://login.microsoftonline.com/your-tenant-id/v2.0',
client_id: 'your-azure-client-id',
client_secret: 'your-azure-client-secret',
redirect_uri: 'https://yourapp.com/callback',
scope: 'openid profile email'
};
Token Management
Access Token Refresh
async function refreshAccessToken(refreshToken) {
const response = await fetch(oidcConfig.token_endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: clientConfig.client_id,
client_secret: clientConfig.client_secret
})
});
const tokens = await response.json();
return tokens;
}
Token Storage
// Secure token storage
function storeTokens(tokens) {
// Store access token in memory (short-lived)
sessionStorage.setItem('access_token', tokens.access_token);
// Store refresh token securely (long-lived)
// Consider using httpOnly cookies or secure storage
localStorage.setItem('refresh_token', tokens.refresh_token);
// Store ID token for user info
sessionStorage.setItem('id_token', tokens.id_token);
}
function clearTokens() {
sessionStorage.removeItem('access_token');
sessionStorage.removeItem('id_token');
localStorage.removeItem('refresh_token');
}
Error Handling
function handleOIDCError(error) {
switch (error.error) {
case 'access_denied':
console.log('User denied access');
break;
case 'invalid_request':
console.log('Invalid request parameters');
break;
case 'invalid_client':
console.log('Invalid client configuration');
break;
case 'invalid_grant':
console.log('Invalid authorization code');
break;
case 'unsupported_response_type':
console.log('Unsupported response type');
break;
case 'invalid_scope':
console.log('Invalid scope requested');
break;
default:
console.log('Unknown OIDC error:', error);
}
}
Security Best Practices
- Use HTTPS: Always use HTTPS in production
- Validate State: Always validate the state parameter
- Use Nonce: Include nonce for replay attack prevention
- Token Validation: Always validate ID tokens on the server
- Secure Storage: Store tokens securely (httpOnly cookies recommended)
- Token Expiration: Implement proper token refresh logic
Test OIDC Flows
Use our OIDC playground to test and debug your authentication flows with various identity providers.
Use OIDC Playground