Files
oicd/docs/API_GUIDE.md
2025-11-30 00:07:24 +01:00

17 KiB

API Integration Guide

Complete guide for developers integrating applications with this OIDC Identity Provider.


Table of Contents

  1. Overview
  2. Getting Started
  3. OIDC Flow
  4. Endpoints Reference
  5. Client Configuration
  6. Code Examples
  7. Testing
  8. 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)
  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:

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

GET /.well-known/openid-configuration

Response:

{
  "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

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

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:

{
  "access_token": "eyJhbGci...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "eyJhbGci...",
  "scope": "openid profile email"
}

ID Token Contents (JWT):

{
  "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

GET /userinfo
Authorization: Bearer {ACCESS_TOKEN}

Response:

{
  "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

GET /jwks

Response:

{
  "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)

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 '<a href="/login">Login</a>'

Node.js (Express + Passport)

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)

// 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');
});

⚠️ 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

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

curl https://your-idp.com/userinfo \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Using the Test Client

This repository includes a test client:

# 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

curl https://your-idp.com/.well-known/openid-configuration | jq

2. Validate ID Token Use 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

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


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