OAuth 2.0 & OpenID Connect Beispiele

Beispiele für das OAuth 2.0 Autorisierungs-Framework und das OpenID Connect Authentifizierungsprotokoll

💻 Resource Owner Password Credentials Fluss javascript

🟢 simple ⭐⭐⭐

Implementierung des OAuth 2.0 Resource Owner Password Credentials Grant

⏱️ 25 min 🏷️ 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"
}
*/

💻 OAuth 2.0 Autorisierungscode-Fluss javascript

🟡 intermediate ⭐⭐⭐⭐

Implementierung des OAuth 2.0 Autorisierungscode-Flusses für sicheren API-Zugriff

⏱️ 30 min 🏷️ 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) Implementierung python

🟡 intermediate ⭐⭐⭐⭐

Vollständige OpenID Connect Implementierung mit ID-Tokens und Benutzerauthentifizierung

⏱️ 35 min 🏷️ openid, jwt, authentication, flask, security
Prerequisites: Python, Flask, 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 }
  });
});