Elysia Tools
Navigation
Authentication & Authorization
OAuth 2.0 & OpenID Connect Samples
OAuth 2.0 authorization framework and OpenID Connect authentication protocol examples
Samples
Entries inside this sample collection
OAuth 2.0 Authorization Code Flow
Implementation of OAuth 2.0 authorization code flow for secure API access
Difficulty
7/10
Estimated time
30 min
Tags
oauth, authentication, authorization, security, express
Prerequisites
Node.js, OAuth 2.0 concepts, Express.js
// OAuth 2.0 Authorization Code Flow
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// OAuth configuration
const oauth = {
client_id: 'your-client-id',
client_secret: 'your-client-secret',
redirect_uri: 'http://localhost:3000/callback',
auth_url: 'https://oauth-provider.com/authorize',
token_url: 'https://oauth-provider.com/token'
};
// Initiate OAuth flow
app.get('/auth', (req, res) => {
const state = crypto.randomBytes(16).toString('hex');
const params = new URLSearchParams({
response_type: 'code',
client_id: oauth.client_id,
redirect_uri: oauth.redirect_uri,
scope: 'read write',
state: state
});
const url = `${oauth.auth_url}?${params.toString()}`;
res.redirect(url);
});
// Handle OAuth callback
app.post('/token', async (req, res) => {
const { code } = req.body;
try {
// Exchange code for tokens
const response = await fetch(oauth.token_url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: oauth.client_id,
client_secret: oauth.client_secret,
redirect_uri: oauth.redirect_uri
})
});
const tokens = await response.json();
res.json(tokens);
} catch (error) {
res.status(400).json({ error: 'Token exchange failed' });
}
});
// Protected API endpoint
function requireToken(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Token required' });
// Validate token (implement your validation logic)
req.token = token;
next();
}
app.get('/api/data', requireToken, (req, res) => {
res.json({ message: 'Protected data', token: req.token });
});
// PKCE support for enhanced security
function generatePKCE() {
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
return { codeVerifier, codeChallenge };
}
// OAuth with PKCE
app.get('/auth-pkce', (req, res) => {
const { codeVerifier, codeChallenge } = generatePKCE();
const params = new URLSearchParams({
response_type: 'code',
client_id: oauth.client_id,
redirect_uri: oauth.redirect_uri,
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
const url = `${oauth.auth_url}?${params.toString()}`;
res.json({
authUrl: url,
codeVerifier,
message: 'Store codeVerifier for token exchange'
});
});
// Token exchange with PKCE
app.post('/token-pkce', async (req, res) => {
const { code, codeVerifier } = req.body;
const response = await fetch(oauth.token_url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: oauth.client_id,
client_secret: oauth.client_secret,
redirect_uri: oauth.redirect_uri,
code_verifier: codeVerifier
})
});
const tokens = await response.json();
res.json(tokens);
});OpenID Connect (OIDC) Implementation
Complete OpenID Connect implementation with ID tokens and user authentication
Difficulty
8/10
Estimated time
35 min
Tags
openid, jwt, authentication, express, security
Prerequisites
Node.js, Express.js, JWT concepts, OIDC knowledge
# OpenID Connect with Node.js
const express = require('express');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// OIDC configuration
const oidc = {
issuer: 'https://accounts.google.com',
client_id: 'your-oidc-client-id',
client_secret: 'your-oidc-secret',
redirect_uri: 'http://localhost:3000/callback'
};
// Verify JWT ID token
function verifyIdToken(token) {
try {
const decoded = jwt.verify(token, oidc.client_secret);
return decoded;
} catch (error) {
throw new Error('Invalid ID token');
}
}
// OIDC login
app.get('/auth/oidc', (req, res) => {
const params = new URLSearchParams({
response_type: 'code',
client_id: oidc.client_id,
redirect_uri: oidc.redirect_uri,
scope: 'openid email profile',
response_mode: 'query'
});
const url = `${oidc.issuer}/o/oauth2/v2/auth?${params.toString()}`;
res.redirect(url);
});
// OIDC callback
app.post('/oidc/callback', async (req, res) => {
const { code } = req.body;
// Exchange code for tokens
const tokenResponse = await fetch(`${oidc.issuer}/oauth2/v4/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
code: code,
client_id: oidc.client_id,
client_secret: oidc.client_secret,
redirect_uri: oidc.redirect_uri,
grant_type: 'authorization_code'
})
});
const tokens = await tokenResponse.json();
// Verify ID token
const idToken = tokens.id_token;
const user = verifyIdToken(idToken);
res.json({
user,
tokens: { accessToken: tokens.access_token }
});
});Resource Owner Password Credentials Flow
OAuth 2.0 resource owner password credentials grant implementation
Difficulty
6/10
Estimated time
25 min
Tags
oauth, password, authentication, jwt, security
Prerequisites
Node.js, JWT, bcrypt, OAuth 2.0 concepts
// OAuth 2.0 Resource Owner Password Credentials Flow
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Configuration
const config = {
jwtSecret: 'your-super-secret-jwt-key',
jwtExpiration: '15m',
refreshTokenExpiration: '7d',
bcryptRounds: 12,
rateLimitWindow: 15 * 60 * 1000, // 15 minutes
rateLimitMax: 5 // max attempts per window
};
// In-memory user database (in production, use a proper database)
const users = new Map();
const refreshTokens = new Set();
const rateLimitStore = new Map();
// Initialize with a test user
async function initializeTestUser() {
const hashedPassword = await bcrypt.hash('password123', config.bcryptRounds);
users.set('[email protected]', {
id: 1,
email: '[email protected]',
password: hashedPassword,
name: 'John Doe',
role: 'user',
permissions: ['read:profile', 'write:profile']
});
// Add admin user
const adminHashedPassword = await bcrypt.hash('admin123', config.bcryptRounds);
users.set('[email protected]', {
id: 2,
email: '[email protected]',
password: adminHashedPassword,
name: 'Admin User',
role: 'admin',
permissions: ['read:profile', 'write:profile', 'read:users', 'write:users']
});
}
// Rate limiting middleware
function rateLimit(req, res, next) {
const clientIp = req.ip || req.connection.remoteAddress;
const now = Date.now();
// Clean old entries
for (const [ip, attempts] of rateLimitStore.entries()) {
if (now - attempts.timestamp > config.rateLimitWindow) {
rateLimitStore.delete(ip);
}
}
const attempts = rateLimitStore.get(clientIp) || { count: 0, timestamp: now };
if (now - attempts.timestamp > config.rateLimitWindow) {
attempts.count = 1;
attempts.timestamp = now;
} else {
attempts.count++;
}
rateLimitStore.set(clientIp, attempts);
if (attempts.count > config.rateLimitMax) {
const remainingTime = Math.ceil((config.rateLimitWindow - (now - attempts.timestamp)) / 1000);
res.set('Retry-After', remainingTime);
return res.status(429).json({
error: 'Too many authentication attempts',
retryAfter: remainingTime
});
}
next();
}
// Generate tokens
function generateTokens(user) {
const payload = {
sub: user.id,
email: user.email,
name: user.name,
role: user.role,
permissions: user.permissions,
type: 'access'
};
const accessToken = jwt.sign(payload, config.jwtSecret, {
expiresIn: config.jwtExpiration,
issuer: 'your-auth-server',
audience: 'your-api'
});
const refreshTokenPayload = {
sub: user.id,
type: 'refresh',
jti: crypto.randomBytes(16).toString('hex') // Unique identifier for refresh token
};
const refreshToken = jwt.sign(refreshTokenPayload, config.jwtSecret, {
expiresIn: config.refreshTokenExpiration
});
refreshTokens.add(refreshToken);
return {
accessToken,
refreshToken,
tokenType: 'Bearer',
expiresIn: 900 // 15 minutes in seconds
};
}
// Verify refresh token
function verifyRefreshToken(token) {
try {
const decoded = jwt.verify(token, config.jwtSecret);
if (decoded.type !== 'refresh') {
throw new Error('Invalid token type');
}
if (!refreshTokens.has(token)) {
throw new Error('Refresh token not found');
}
return decoded;
} catch (error) {
throw new Error('Invalid refresh token');
}
}
// Generate scope-based access token
function generateScopedToken(user, requestedScopes) {
// Define available scopes and what they grant
const availableScopes = {
'read:profile': 'Read user profile information',
'write:profile': 'Update user profile information',
'read:users': 'Read user list (admin only)',
'write:users': 'Manage users (admin only)',
'admin': 'Full administrative access'
};
// Validate requested scopes
const validScopes = [];
for (const scope of requestedScopes) {
if (user.permissions.includes(scope) ||
(scope === 'admin' && user.role === 'admin')) {
validScopes.push(scope);
}
}
const payload = {
sub: user.id,
email: user.email,
name: user.name,
role: user.role,
scopes: validScopes,
type: 'access'
};
return jwt.sign(payload, config.jwtSecret, {
expiresIn: config.jwtExpiration,
issuer: 'your-auth-server',
audience: 'your-api'
});
}
// OAuth 2.0 Token Endpoint
app.post('/oauth/token', rateLimit, async (req, res) => {
const { grant_type, username, password, scope, refresh_token } = req.body;
try {
switch (grant_type) {
case 'password':
// Resource Owner Password Credentials Grant
if (!username || !password) {
return res.status(400).json({
error: 'invalid_request',
error_description: 'username and password are required'
});
}
const user = users.get(username);
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({
error: 'invalid_grant',
error_description: 'Invalid username or password'
});
}
// Handle requested scopes
const requestedScopes = scope ? scope.split(' ') : ['read:profile'];
const accessToken = generateScopedToken(user, requestedScopes);
const tokenData = generateTokens(user);
return res.json({
access_token: accessToken,
refresh_token: tokenData.refreshToken,
token_type: 'Bearer',
expires_in: 900,
scope: requestedScopes.join(' ')
});
case 'refresh_token':
// Refresh Token Grant
if (!refresh_token) {
return res.status(400).json({
error: 'invalid_request',
error_description: 'refresh_token is required'
});
}
try {
const decoded = verifyRefreshToken(refresh_token);
const user = Array.from(users.values()).find(u => u.id === decoded.sub);
if (!user) {
return res.status(401).json({
error: 'invalid_grant',
error_description: 'User not found'
});
}
// Revoke old refresh token
refreshTokens.delete(refresh_token);
// Generate new tokens
const newTokens = generateTokens(user);
res.json({
access_token: newTokens.accessToken,
refresh_token: newTokens.refreshToken,
token_type: 'Bearer',
expires_in: 900
});
} catch (error) {
return res.status(401).json({
error: 'invalid_grant',
error_description: 'Invalid refresh token'
});
}
default:
return res.status(400).json({
error: 'unsupported_grant_type',
error_description: 'Only password and refresh_token grants are supported'
});
}
} catch (error) {
console.error('Token endpoint error:', error);
return res.status(500).json({
error: 'server_error',
error_description: 'Internal server error'
});
}
});
// Token Revocation
app.post('/oauth/revoke', (req, res) => {
const { token, token_type_hint } = req.body;
if (!token) {
return res.status(400).json({
error: 'invalid_request',
error_description: 'token is required'
});
}
try {
if (token_type_hint === 'refresh_token') {
refreshTokens.delete(token);
} else {
// For access tokens, we could maintain a blacklist
// For this demo, we'll just accept it
}
res.json({});
} catch (error) {
res.status(500).json({
error: 'server_error',
error_description: 'Failed to revoke token'
});
}
});
// Introspection Endpoint
app.post('/oauth/introspect', (req, res) => {
const { token, token_type_hint } = req.body;
if (!token) {
return res.status(400).json({
error: 'invalid_request',
error_description: 'token is required'
});
}
try {
const decoded = jwt.verify(token, config.jwtSecret);
const response = {
active: true,
scope: decoded.scopes?.join(' ') || 'read:profile',
client_id: 'your-client-id',
sub: decoded.sub.toString(),
exp: decoded.exp,
iat: decoded.iat,
token_type: 'Bearer'
};
res.json(response);
} catch (error) {
res.json({
active: false
});
}
});
// Protected API endpoints
function authenticateToken(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: 'invalid_token',
error_description: 'Access token required'
});
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, config.jwtSecret);
if (decoded.type !== 'access') {
throw new Error('Invalid token type');
}
req.user = {
id: decoded.sub,
email: decoded.email,
name: decoded.name,
role: decoded.role,
scopes: decoded.scopes || ['read:profile']
};
next();
} catch (error) {
return res.status(401).json({
error: 'invalid_token',
error_description: 'Invalid or expired token'
});
}
}
function requireScope(scope) {
return (req, res, next) => {
if (!req.user.scopes.includes(scope) && !req.user.scopes.includes('admin')) {
return res.status(403).json({
error: 'insufficient_scope',
error_description: `Required scope: ${scope}`
});
}
next();
};
}
// Profile endpoint
app.get('/api/profile', authenticateToken, requireScope('read:profile'), (req, res) => {
const user = users.get(req.user.email);
res.json({
id: user.id,
email: user.email,
name: user.name,
role: user.role,
scopes: req.user.scopes
});
});
// Update profile endpoint
app.put('/api/profile', authenticateToken, requireScope('write:profile'), (req, res) => {
const { name } = req.body;
if (!name) {
return res.status(400).json({
error: 'invalid_request',
error_description: 'name is required'
});
}
const user = users.get(req.user.email);
user.name = name;
res.json({
message: 'Profile updated successfully',
user: {
id: user.id,
email: user.email,
name: user.name
}
});
});
// Admin endpoint to list users
app.get('/api/users', authenticateToken, requireScope('read:users'), (req, res) => {
const userList = Array.from(users.values()).map(user => ({
id: user.id,
email: user.email,
name: user.name,
role: user.role
}));
res.json({ users: userList });
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
version: '1.0.0'
});
});
// Initialize and start server
initializeTestUser().then(() => {
app.listen(3000, () => {
console.log('OAuth 2.0 Resource Owner Password Credentials server running on port 3000');
});
});
// Example client usage:
/*
// Get access token with password grant
POST /oauth/token
{
"grant_type": "password",
"username": "[email protected]",
"password": "password123",
"scope": "read:profile write:profile"
}
// Refresh token
POST /oauth/token
{
"grant_type": "refresh_token",
"refresh_token": "your-refresh-token"
}
// Access protected resource
GET /api/profile
Authorization: Bearer your-access-token
// Revoke token
POST /oauth/revoke
{
"token": "your-refresh-token",
"token_type_hint": "refresh_token"
}
*/Tools
Tools frequently paired with this sample
Related