first commit

This commit is contained in:
2025-11-30 00:07:24 +01:00
commit b5e642aecb
78 changed files with 15162 additions and 0 deletions

622
docs/API_GUIDE.md Normal file
View File

@ -0,0 +1,622 @@
# 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 '<a href="/login">Login</a>'
```
---
### 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