17 KiB
API Integration Guide
Complete guide for developers integrating applications with this OIDC Identity Provider.
Table of Contents
- Overview
- Getting Started
- OIDC Flow
- Endpoints Reference
- Client Configuration
- Code Examples
- Testing
- 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
- OIDC Provider Running: Deploy this IdP (see Deployment Guide)
- Admin Access: You need admin credentials to register your application
- HTTPS (Production): Required for secure cookie handling
Step 1: Register Your Application
-
Navigate to the admin panel:
https://your-idp.com/admin/login -
Log in with admin credentials
-
Go to Clients → Create New Client
-
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
-
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/callbackhttp://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');
});
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
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:
- Check admin panel → your client → registered redirect URIs
- Ensure exact match (including trailing slash)
- Use URL encoding for query parameters
invalid_client
Problem: Client ID or secret is incorrect.
Solution:
- Verify client ID and secret from admin panel
- Check for typos or whitespace
- Ensure client exists and is active
invalid_grant
Problem: Authorization code is invalid, expired, or already used.
Solution:
- Authorization codes expire in 10 minutes
- Codes can only be used once
- Restart the flow if code expired
access_denied
Problem: User denied authorization or login failed.
Solution:
- User may have clicked "Cancel"
- Check credentials
- 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
stateparameter to prevent CSRF attacks - ✅ Store client secret securely (environment variables, not in code)
- ✅ Validate ID token signature using JWKS endpoint
- ✅ Check token expiration (
expclaim) - ✅ 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
stateparameter 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/
- Architecture: ARCHITECTURE.md
- Deployment: 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