# API Integration Guide Complete guide for developers integrating applications with this OIDC Identity Provider. --- ## Table of Contents 1. [Overview](#overview) 2. [Getting Started](#getting-started) 3. [OIDC Flow](#oidc-flow) 4. [Endpoints Reference](#endpoints-reference) 5. [Client Configuration](#client-configuration) 6. [Code Examples](#code-examples) 7. [Testing](#testing) 8. [Troubleshooting](#troubleshooting) --- ## Overview This OIDC Identity Provider implements the **Authorization Code Flow**, which is the most secure OAuth 2.0 / OpenID Connect flow suitable for server-side applications. ### What You Get - **User Authentication**: Delegate authentication to this IdP - **User Information**: Retrieve user profile (email, name, etc.) - **Single Sign-On (SSO)**: Users log in once, access multiple applications - **Secure Tokens**: RS256-signed ID tokens and access tokens ### Supported Grant Types - ✅ Authorization Code Flow (recommended) - ❌ Implicit Flow (not supported - insecure) - ❌ Client Credentials (not yet implemented) - ❌ Refresh Tokens (not yet implemented) --- ## Getting Started ### Prerequisites 1. **OIDC Provider Running**: Deploy this IdP (see [Deployment Guide](deployment.md)) 2. **Admin Access**: You need admin credentials to register your application 3. **HTTPS (Production)**: Required for secure cookie handling ### Step 1: Register Your Application 1. Navigate to the admin panel: `https://your-idp.com/admin/login` 2. Log in with admin credentials 3. Go to **Clients** → **Create New Client** 4. Fill in the form: - **Client Name**: Your application name (e.g., "My Web App") - **Redirect URIs**: Where users return after login (e.g., `https://myapp.com/callback`) - **Allowed Scopes**: `openid profile email` 5. **Save the credentials**: ``` Client ID: abc123... Client Secret: xyz789... (shown only once!) ``` ### Step 2: Discover OIDC Configuration Fetch the OIDC discovery document: ```bash curl https://your-idp.com/.well-known/openid-configuration ``` This returns all endpoint URLs and supported features. --- ## OIDC Flow ### Authorization Code Flow (Step by Step) ``` ┌─────────┐ ┌─────────────┐ │ User │ │ Your App │ └────┬────┘ └──────┬──────┘ │ │ │ 1. Click "Login" │ │───────────────────────────────────────────────────>│ │ │ │ 2. Redirect to /authorize │ │<───────────────────────────────────────────────────│ │ │ ┌────┴────┐ ┌─────┴───────┐ │ User │ │ OIDC IdP │ └────┬────┘ └──────┬──────┘ │ │ │ 3. Login form shown │ │<───────────────────────────────────────────────────│ │ │ │ 4. Submit credentials │ │───────────────────────────────────────────────────>│ │ │ │ 5. Redirect to callback with code │ │<───────────────────────────────────────────────────│ │ │ ┌────┴────┐ ┌─────┴───────┐ │ User │ │ Your App │ └────┬────┘ └──────┬──────┘ │ 6. Return to app │ │───────────────────────────────────────────────────>│ │ │ │ ┌──────┴──────┐ │ │ OIDC IdP │ │ └──────┬──────┘ │ │ │ 7. Exchange code for tokens │ │ <─────────────────────────────│ │ │ │ 8. Return tokens │ │ ─────────────────────────────>│ │ │ │ 9. Logged in! │ │<───────────────────────────────────────────────────│ │ │ ``` --- ## Endpoints Reference ### 1. Discovery Endpoint **Get OIDC Configuration** ```http GET /.well-known/openid-configuration ``` **Response:** ```json { "issuer": "https://your-idp.com", "authorization_endpoint": "https://your-idp.com/authorize", "token_endpoint": "https://your-idp.com/token", "userinfo_endpoint": "https://your-idp.com/userinfo", "jwks_uri": "https://your-idp.com/jwks", "response_types_supported": ["code"], "id_token_signing_alg_values_supported": ["RS256"], "scopes_supported": ["openid", "profile", "email"] } ``` --- ### 2. Authorization Endpoint **Initiate Login Flow** ```http GET /authorize?client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&response_type=code&scope=openid%20profile%20email&state={STATE} ``` **Parameters:** | Parameter | Required | Description | |-----------|----------|-------------| | `client_id` | ✅ Yes | Your client ID | | `redirect_uri` | ✅ Yes | Where to redirect after login (must match registered URI) | | `response_type` | ✅ Yes | Must be `code` | | `scope` | ✅ Yes | Space-separated scopes (must include `openid`) | | `state` | ⚠️ Recommended | CSRF protection token (you generate this) | **Example:** ``` https://your-idp.com/authorize? client_id=abc123& redirect_uri=https://myapp.com/callback& response_type=code& scope=openid%20profile%20email& state=random_csrf_token_123 ``` **Response:** User is redirected to login page. After successful login, redirected to: ``` https://myapp.com/callback?code=AUTH_CODE_HERE&state=random_csrf_token_123 ``` --- ### 3. Token Endpoint **Exchange Authorization Code for Tokens** ```http POST /token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& code=AUTH_CODE& redirect_uri=https://myapp.com/callback& client_id=abc123& client_secret=xyz789 ``` **Parameters:** | Parameter | Required | Description | |-----------|----------|-------------| | `grant_type` | ✅ Yes | Must be `authorization_code` | | `code` | ✅ Yes | Authorization code from callback | | `redirect_uri` | ✅ Yes | Same URI used in authorization request | | `client_id` | ✅ Yes | Your client ID | | `client_secret` | ✅ Yes | Your client secret | **Response:** ```json { "access_token": "eyJhbGci...", "token_type": "Bearer", "expires_in": 3600, "id_token": "eyJhbGci...", "scope": "openid profile email" } ``` **ID Token Contents (JWT):** ```json { "iss": "https://your-idp.com", "sub": "user-123", "aud": "abc123", "exp": 1234567890, "iat": 1234567890, "email": "user@example.com", "name": "John Doe", "preferred_username": "john" } ``` --- ### 4. UserInfo Endpoint **Get User Information** ```http GET /userinfo Authorization: Bearer {ACCESS_TOKEN} ``` **Response:** ```json { "sub": "user-123", "username": "john", "email": "john@example.com", "name": "John Doe", "preferred_username": "john", "role": "user", "permissions": ["read:data"] } ``` --- ### 5. JWKS Endpoint **Get Public Keys for Token Verification** ```http GET /jwks ``` **Response:** ```json { "keys": [ { "kty": "RSA", "use": "sig", "kid": "...", "n": "...", "e": "AQAB" } ] } ``` --- ## Client Configuration ### Redirect URI Rules ✅ **Allowed:** - `https://myapp.com/callback` - `http://localhost:8080/callback` (development only) - `https://myapp.com/auth/oidc/callback` ❌ **Not Allowed:** - Wildcard URIs (`https://*.myapp.com/callback`) - Non-HTTP(S) schemes (`myapp://callback`) ### Scopes | Scope | Description | User Info Included | |-------|-------------|--------------------| | `openid` | **Required** - Enables OIDC | `sub` | | `profile` | User profile information | `name`, `preferred_username` | | `email` | User email address | `email` | --- ## Code Examples ### Python (Flask + Authlib) ```python from flask import Flask, redirect, url_for, session from authlib.integrations.flask_client import OAuth app = Flask(__name__) app.secret_key = 'your-secret-key' oauth = OAuth(app) oauth.register( name='oidc', client_id='YOUR_CLIENT_ID', client_secret='YOUR_CLIENT_SECRET', server_metadata_url='https://your-idp.com/.well-known/openid-configuration', client_kwargs={'scope': 'openid profile email'} ) @app.route('/login') def login(): redirect_uri = url_for('callback', _external=True) return oauth.oidc.authorize_redirect(redirect_uri) @app.route('/callback') def callback(): token = oauth.oidc.authorize_access_token() user_info = token['userinfo'] session['user'] = user_info return redirect('/') @app.route('/') def index(): user = session.get('user') if user: return f"Hello, {user['name']}!" return 'Login' ``` --- ### Node.js (Express + Passport) ```javascript const express = require('express'); const passport = require('passport'); const { Strategy } = require('openid-client'); const { Issuer } = require('openid-client'); const app = express(); // Discover OIDC provider Issuer.discover('https://your-idp.com/.well-known/openid-configuration') .then(issuer => { const client = new issuer.Client({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', redirect_uris: ['http://localhost:3000/callback'], response_types: ['code'], }); passport.use('oidc', new Strategy({ client }, (tokenSet, userinfo, done) => { return done(null, userinfo); })); app.get('/login', passport.authenticate('oidc')); app.get('/callback', passport.authenticate('oidc', { successRedirect: '/', failureRedirect: '/login' }) ); }); app.listen(3000); ``` --- ### PHP (Laravel Socialite) ```php // config/services.php 'oidc' => [ 'client_id' => env('OIDC_CLIENT_ID'), 'client_secret' => env('OIDC_CLIENT_SECRET'), 'redirect' => env('OIDC_REDIRECT_URI'), 'base_url' => env('OIDC_ISSUER'), ], // routes/web.php Route::get('/login', function () { return Socialite::driver('oidc')->redirect(); }); Route::get('/callback', function () { $user = Socialite::driver('oidc')->user(); // $user->name // $user->email // $user->token (access token) Auth::login($user); return redirect('/dashboard'); }); ``` --- ### JavaScript (SPA - NOT RECOMMENDED) ⚠️ **Warning**: Authorization Code Flow requires a backend to keep the client secret secure. For SPAs, consider using **PKCE** (not yet implemented) or a backend-for-frontend (BFF) pattern. **BFF Pattern (Recommended for SPAs):** ``` [React/Vue App] <--> [Your Node.js Backend] <--> [OIDC IdP] (handles OIDC flow) ``` --- ## Testing ### Manual Testing with cURL **Step 1: Get Authorization Code** Open in browser: ``` https://your-idp.com/authorize?client_id=test-client&redirect_uri=http://localhost:8080/callback&response_type=code&scope=openid%20profile%20email&state=test123 ``` After login, you'll be redirected to: ``` http://localhost:8080/callback?code=ABC123&state=test123 ``` **Step 2: Exchange Code for Token** ```bash curl -X POST https://your-idp.com/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=ABC123" \ -d "redirect_uri=http://localhost:8080/callback" \ -d "client_id=test-client" \ -d "client_secret=YOUR_SECRET" ``` **Step 3: Get User Info** ```bash curl https://your-idp.com/userinfo \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` --- ### Using the Test Client This repository includes a test client: ```bash # Start the test client python3 test_client.py # Open browser open http://localhost:8080 # Click "Login with OIDC" ``` --- ## Troubleshooting ### Common Errors #### `invalid_redirect_uri` **Problem**: The redirect URI doesn't match registered URIs. **Solution**: 1. Check admin panel → your client → registered redirect URIs 2. Ensure exact match (including trailing slash) 3. Use URL encoding for query parameters #### `invalid_client` **Problem**: Client ID or secret is incorrect. **Solution**: 1. Verify client ID and secret from admin panel 2. Check for typos or whitespace 3. Ensure client exists and is active #### `invalid_grant` **Problem**: Authorization code is invalid, expired, or already used. **Solution**: 1. Authorization codes expire in 10 minutes 2. Codes can only be used once 3. Restart the flow if code expired #### `access_denied` **Problem**: User denied authorization or login failed. **Solution**: 1. User may have clicked "Cancel" 2. Check credentials 3. Verify user account is active --- ### Debugging Tips **1. Check Discovery Document** ```bash curl https://your-idp.com/.well-known/openid-configuration | jq ``` **2. Validate ID Token** Use [jwt.io](https://jwt.io) to decode and verify the token structure. **3. Enable Debug Logging** Set `LOG_LEVEL=DEBUG` in your application to see detailed OIDC flow logs. **4. Check Server Logs** ```bash docker-compose -f docker-compose.prod.yml logs -f oidc_server ``` --- ## Security Best Practices ### ✅ Do This - ✅ **Use HTTPS** in production (required for secure cookies) - ✅ **Validate `state` parameter** to prevent CSRF attacks - ✅ **Store client secret securely** (environment variables, not in code) - ✅ **Validate ID token signature** using JWKS endpoint - ✅ **Check token expiration** (`exp` claim) - ✅ **Use short-lived access tokens** (default: 1 hour) - ✅ **Implement token refresh** (when available) ### ❌ Don't Do This - ❌ **Don't use Implicit Flow** (insecure, deprecated) - ❌ **Don't store tokens in localStorage** (use httpOnly cookies or sessionStorage) - ❌ **Don't expose client secret** in frontend code - ❌ **Don't skip `state` parameter** validation - ❌ **Don't accept tokens without verification** - ❌ **Don't use HTTP** in production --- ## Rate Limiting Sensitive endpoints are rate-limited: | Endpoint | Limit | |----------|-------| | `/token` | 10 requests per minute | | `/login` | 5 requests per minute | | `/register` | 3 requests per hour | **Response when rate-limited:** ``` HTTP 429 Too Many Requests Retry-After: 60 ``` --- ## Support - **Documentation**: [docs/](../docs/) - **Architecture**: [ARCHITECTURE.md](ARCHITECTURE.md) - **Deployment**: [deployment.md](deployment.md) - **Issues**: Report bugs via GitHub issues --- ## Appendix ### Token Lifetimes | Token Type | Default Lifetime | Configurable | |------------|------------------|--------------| | Authorization Code | 10 minutes | `AUTHORIZATION_CODE_LIFETIME` | | Access Token | 1 hour | `ACCESS_TOKEN_LIFETIME` | | ID Token | 1 hour | `ID_TOKEN_LIFETIME` | ### Supported Claims | Claim | Description | Scope Required | |-------|-------------|----------------| | `sub` | User ID (unique identifier) | `openid` | | `email` | User email address | `email` | | `name` | User full name | `profile` | | `preferred_username` | Display username | `profile` | | `role` | User role | `openid` | | `permissions` | User permissions array | `openid` | --- **Last Updated**: 2025-11-28 **Version**: 1.0.0