first commit
This commit is contained in:
622
docs/API_GUIDE.md
Normal file
622
docs/API_GUIDE.md
Normal 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
|
||||
79
docs/ARCHITECTURE.md
Normal file
79
docs/ARCHITECTURE.md
Normal file
@ -0,0 +1,79 @@
|
||||
# Application Architecture
|
||||
|
||||
This document provides a detailed overview of the OIDC server's internal architecture. For a general overview, see the `README.md` file.
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
The application is a standard Flask web server that follows a monolithic architecture. It is designed to be run as a containerized service using Docker.
|
||||
|
||||
The main components are:
|
||||
|
||||
- **Flask Application (`oidc_server.py`)**: The core of the application, which handles all incoming requests, business logic, and OIDC flows.
|
||||
- **Database (`models.py`)**: A PostgreSQL or SQLite database, managed by SQLAlchemy, that persists all data, including users, clients, tokens, and logs.
|
||||
- **Configuration (`config.py` & `.env`)**: A flexible, environment-based configuration system for managing settings and secrets.
|
||||
- **Templates (`templates.py` & `admin_templates.py`)**: In-memory HTML templates for rendering the user interface.
|
||||
|
||||
## 2. Configuration System
|
||||
|
||||
The application uses a layered configuration approach to separate concerns and keep secrets out of the codebase.
|
||||
|
||||
- **.env File**: This file (which is not committed to version control) is used to store all secrets and environment-specific settings. It is loaded at startup using `python-dotenv`.
|
||||
- **`config.py`**: This file defines several configuration classes (`DevelopmentConfig`, `ProductionConfig`, `TestingConfig`) that inherit from a base `Config` class. It reads values from the environment (populated by the `.env` file) and sets sane defaults.
|
||||
- **`oidc_server.py`**: At startup, the main application file reads the `FLASK_ENV` environment variable to determine which configuration class to load from `config.py`. This ensures that the correct settings (e.g., database URI, debug mode) are used for the environment.
|
||||
|
||||
## 3. Application Structure (`oidc_server.py`)
|
||||
|
||||
The main application file is responsible for:
|
||||
|
||||
1. **Initialization**:
|
||||
- Creating the Flask `app` instance.
|
||||
- Loading the correct configuration object.
|
||||
- Initializing the database connection (`db.init_app(app)`).
|
||||
- Initializing `Flask-Migrate` for database schema management.
|
||||
- Initializing `Flask-Limiter` for rate limiting.
|
||||
|
||||
2. **Routing**: All of the application's routes are defined here. They can be grouped into:
|
||||
- **OIDC Endpoints**: Standard endpoints required by the OpenID Connect specification (`/authorize`, `/token`, `/userinfo`, `/.well-known/openid-configuration`, `/jwks`).
|
||||
- **User-Facing Pages**: Routes for user interaction, such as `/login`, `/register`, and `/dashboard`.
|
||||
- **Admin Panel**: A set of routes under the `/admin/` prefix for managing users and clients. These routes are protected by the `@admin_required` decorator.
|
||||
|
||||
3. **CLI Commands**: The application defines custom `flask` commands, such as:
|
||||
- `flask db`: For managing database migrations (e.g., `flask db upgrade`).
|
||||
- `flask seed`: For populating the database with initial test data (users and clients).
|
||||
|
||||
## 4. Database Models (`models.py`)
|
||||
|
||||
All data is stored in a relational database, and the schema is defined using SQLAlchemy ORM models.
|
||||
|
||||
- **`User`**: Stores user information, including a hashed password (using `bcrypt`), role, and a flexible JSON-based permissions list.
|
||||
- **`Client`**: Stores information about OIDC client applications. Each client has a `client_id`, a hashed `client_secret`, a list of allowed `redirect_uris`, and a list of `allowed_scopes`.
|
||||
- **`AuthorizationCode`**: A temporary, single-use code that is issued during the first leg of the OIDC flow. It has a short TTL and is marked as used after it is exchanged for a token.
|
||||
- **`AccessToken`**: A token that grants access to the `/userinfo` endpoint. It has a configurable lifetime and can be revoked.
|
||||
- **`AuditLog`**: Records important security-related events, such as login attempts, user creation, and client modifications.
|
||||
|
||||
## 5. OIDC Authorization Code Flow
|
||||
|
||||
The core logic of the IdP is its implementation of the OIDC Authorization Code Flow.
|
||||
|
||||
1. **`/authorize` (GET)**:
|
||||
- A client application redirects the user to this endpoint.
|
||||
- The server validates the `client_id` and `redirect_uri` against the `Client` table in the database.
|
||||
- It stores the authorization request parameters in the user's session and displays a login page.
|
||||
|
||||
2. **`/authorize` (POST)**:
|
||||
- The user submits their credentials.
|
||||
- The server validates the username and password against the `User` table.
|
||||
- On success, it generates a new `AuthorizationCode`, saves it to the database, and redirects the user back to the client's `redirect_uri` with the code included as a query parameter.
|
||||
|
||||
3. **`/token` (POST)**:
|
||||
- The client application makes a direct, back-channel request to this endpoint, sending the authorization code along with its `client_id` and `client_secret`.
|
||||
- The server validates the client's credentials and the authorization code.
|
||||
- It marks the authorization code as used.
|
||||
- It generates a new `AccessToken` and an `id_token` (a JWT signed with the `RS256` algorithm).
|
||||
- It returns the tokens to the client in a JSON response.
|
||||
|
||||
4. **`/userinfo` (GET)**:
|
||||
- The client can use the `AccessToken` to request information about the user from this endpoint.
|
||||
- The server validates the access token and returns the user's claims (e.g., name, email).
|
||||
|
||||
This flow ensures that the user's credentials are never exposed to the client application and that tokens are securely issued and validated.
|
||||
285
docs/PRODUCTION_READY.md
Normal file
285
docs/PRODUCTION_READY.md
Normal file
@ -0,0 +1,285 @@
|
||||
# Production Deployment - Ready to Deploy! 🚀
|
||||
|
||||
Your OIDC Identity Provider is now production-ready with minimal configuration needed.
|
||||
|
||||
## What Was Created
|
||||
|
||||
### 1. Production Configuration Files
|
||||
|
||||
- **`.env.production`** - Production environment template with secure generated secrets
|
||||
- **`docker-compose.prod.yml`** - Production Docker Compose with PostgreSQL and optional Nginx
|
||||
- **`deploy.sh`** - Automated deployment script
|
||||
- **`DEPLOYMENT.md`** - Comprehensive deployment guide
|
||||
|
||||
### 2. Nginx Reverse Proxy (Optional)
|
||||
|
||||
- **`nginx/nginx.conf`** - Production-ready Nginx config with:
|
||||
- HTTPS support (ready for Let's Encrypt)
|
||||
- Security headers
|
||||
- Rate limiting
|
||||
- HTTP → HTTPS redirect
|
||||
|
||||
### 3. Security Features Already Included
|
||||
|
||||
✅ Strong generated secrets (SECRET_KEY, OIDC_CLIENT_SECRET, POSTGRES_PASSWORD)
|
||||
✅ PostgreSQL database with secure password
|
||||
✅ Bcrypt password hashing
|
||||
✅ Rate limiting on login endpoints
|
||||
✅ Audit logging
|
||||
✅ Health checks
|
||||
✅ Session security
|
||||
✅ Non-root Docker user
|
||||
|
||||
## Quick Deployment (3 Steps)
|
||||
|
||||
### Step 1: Configure Environment
|
||||
|
||||
```bash
|
||||
# Copy production env file
|
||||
cp .env.production .env
|
||||
|
||||
# Edit OIDC_ISSUER with your domain/IP
|
||||
nano .env
|
||||
# Change: OIDC_ISSUER=http://YOUR_SERVER_IP:5000
|
||||
# Or: OIDC_ISSUER=https://auth.yourdomain.com
|
||||
```
|
||||
|
||||
### Step 2: Deploy
|
||||
|
||||
```bash
|
||||
# Run deployment script
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
### Step 3: Secure Admin Account
|
||||
|
||||
```bash
|
||||
# Visit admin panel
|
||||
# Default: admin/admin123
|
||||
# CHANGE PASSWORD IMMEDIATELY!
|
||||
```
|
||||
|
||||
Access: `http://YOUR_SERVER:5000/admin/login`
|
||||
|
||||
## What's Ready Out of the Box
|
||||
|
||||
✅ **OIDC Authorization Code Flow**
|
||||
✅ **User Registration & Management**
|
||||
✅ **Admin Dashboard** with CRUD operations
|
||||
✅ **Role-based Access Control** (admin, user, moderator, readonly)
|
||||
✅ **Permission System** (JSON array of permissions)
|
||||
✅ **Audit Logging** (login attempts, admin actions)
|
||||
✅ **Health Monitoring** endpoint at `/health`
|
||||
✅ **Rate Limiting** on sensitive endpoints
|
||||
✅ **PostgreSQL Database** with persistent storage
|
||||
✅ **Docker Compose** deployment
|
||||
✅ **Gunicorn WSGI Server** (production-ready)
|
||||
✅ **Automatic Database Initialization** with default users
|
||||
|
||||
## Generated Secrets (Already in .env.production)
|
||||
|
||||
- **SECRET_KEY**: `8a84ce2f0be5f7062f5329d93032c95612547928fe97490e2ca63dea12cc8558`
|
||||
- **OIDC_CLIENT_SECRET**: `nQT_E5iVbsGVOcLi8-yHxIF_sgG7UccHMv2GgvBEQ_g`
|
||||
- **POSTGRES_PASSWORD**: `P_QbECpV03H6P9zQNuyu0lyLdOySrlr7Rr9HNpVG3aw`
|
||||
|
||||
⚠️ These are cryptographically secure random values. You can use them as-is or regenerate new ones.
|
||||
|
||||
## Deployment Options
|
||||
|
||||
### Option A: Simple Deployment (HTTP, No Nginx)
|
||||
|
||||
Perfect for:
|
||||
- Internal homelab networks
|
||||
- Testing
|
||||
- Behind existing reverse proxy
|
||||
|
||||
1. Edit `.env` → set OIDC_ISSUER
|
||||
2. Run `./deploy.sh`
|
||||
3. Access at port 5000
|
||||
|
||||
### Option B: Full Production with HTTPS (Nginx)
|
||||
|
||||
Perfect for:
|
||||
- Public-facing deployments
|
||||
- Production environments
|
||||
- Maximum security
|
||||
|
||||
1. Generate SSL certificates (Let's Encrypt)
|
||||
2. Edit `nginx/nginx.conf` → set your domain
|
||||
3. Edit `.env` → set HTTPS OIDC_ISSUER
|
||||
4. Run `./deploy.sh`
|
||||
5. Access at port 443 (HTTPS)
|
||||
|
||||
See `DEPLOYMENT.md` for detailed instructions.
|
||||
|
||||
## Default Users
|
||||
|
||||
Created automatically on first run:
|
||||
|
||||
**Admin User:**
|
||||
- Username: `admin`
|
||||
- Password: `admin123`
|
||||
- Role: admin
|
||||
- Permissions: read:data, write:data, manage:users, manage:settings
|
||||
|
||||
**Test User:**
|
||||
- Username: `test`
|
||||
- Password: `test123`
|
||||
- Role: user
|
||||
- Permissions: read:data
|
||||
|
||||
⚠️ **CRITICAL**: Change admin password immediately after deployment!
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Health Check
|
||||
|
||||
```bash
|
||||
curl http://localhost:5000/health
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"database": "healthy",
|
||||
"timestamp": "2025-11-21T...",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.prod.yml logs -f
|
||||
```
|
||||
|
||||
### Database Backups
|
||||
|
||||
```bash
|
||||
mkdir -p backups
|
||||
docker exec oidc_postgres pg_dump -U oidc_user oidc_db > backups/backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
## Management Commands
|
||||
|
||||
```bash
|
||||
# Start services
|
||||
./deploy.sh
|
||||
|
||||
# Stop services
|
||||
docker-compose -f docker-compose.prod.yml down
|
||||
|
||||
# Restart services
|
||||
docker-compose -f docker-compose.prod.yml restart
|
||||
|
||||
# View status
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
|
||||
# Update application
|
||||
git pull
|
||||
docker-compose -f docker-compose.prod.yml up -d --build
|
||||
```
|
||||
|
||||
## OIDC Endpoints
|
||||
|
||||
Once deployed, your clients can use:
|
||||
|
||||
**Discovery:**
|
||||
```
|
||||
{OIDC_ISSUER}/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
**Authorization:**
|
||||
```
|
||||
{OIDC_ISSUER}/authorize
|
||||
```
|
||||
|
||||
**Token Exchange:**
|
||||
```
|
||||
{OIDC_ISSUER}/token
|
||||
```
|
||||
|
||||
**UserInfo:**
|
||||
```
|
||||
{OIDC_ISSUER}/userinfo
|
||||
```
|
||||
|
||||
## Client Configuration Example
|
||||
|
||||
For applications connecting to your OIDC provider:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"issuer": "https://auth.yourdomain.com",
|
||||
"client_id": "homelab-client", // From .env: OIDC_CLIENT_ID
|
||||
"client_secret": "nQT_E5iVbsGVOcLi8-yHxIF_sgG7UccHMv2GgvBEQ_g", // From .env
|
||||
"redirect_uri": "https://your-app.com/callback",
|
||||
"response_type": "code",
|
||||
"scope": "openid profile email"
|
||||
}
|
||||
```
|
||||
|
||||
## What's NOT Included Yet (Future Enhancements)
|
||||
|
||||
These are planned but not required for basic production:
|
||||
|
||||
- ⏳ Refresh Token Flow (TODO #2)
|
||||
- ⏳ RS256/RSA JWT Signing (TODO #1) - currently uses HS256
|
||||
- ⏳ Multi-Client Database Support (TODO #6) - currently one hardcoded client
|
||||
- ⏳ Email Verification (TODO #8)
|
||||
- ⏳ 2FA/MFA (TODO #9)
|
||||
- ⏳ PKCE Support (TODO #5)
|
||||
|
||||
See `TODO.md` for complete roadmap.
|
||||
|
||||
## Security Checklist Before Going Live
|
||||
|
||||
- [ ] Changed default admin password
|
||||
- [ ] Reviewed generated secrets in .env
|
||||
- [ ] Set correct OIDC_ISSUER (your domain)
|
||||
- [ ] Configured HTTPS (if public-facing)
|
||||
- [ ] Set up firewall rules
|
||||
- [ ] Configured database backups
|
||||
- [ ] Tested health endpoint
|
||||
- [ ] Tested complete OIDC flow
|
||||
- [ ] Reviewed audit logs
|
||||
- [ ] Set up monitoring/alerting
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
See `DEPLOYMENT.md` Section "Troubleshooting" for detailed solutions.
|
||||
|
||||
Quick checks:
|
||||
```bash
|
||||
# Services running?
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
|
||||
# Health check passing?
|
||||
curl http://localhost:5000/health
|
||||
|
||||
# Database accessible?
|
||||
docker exec oidc_postgres pg_isready -U oidc_user -d oidc_db
|
||||
|
||||
# Check logs
|
||||
docker-compose -f docker-compose.prod.yml logs
|
||||
```
|
||||
|
||||
## Support & Documentation
|
||||
|
||||
- **Deployment Guide**: `DEPLOYMENT.md`
|
||||
- **Architecture Details**: `CLAUDE.md`
|
||||
- **Feature Roadmap**: `TODO.md`
|
||||
- **README**: `README.md`
|
||||
|
||||
## You're Ready! 🎉
|
||||
|
||||
Your OIDC Identity Provider is production-ready. Just:
|
||||
|
||||
1. Copy `.env.production` to `.env`
|
||||
2. Edit OIDC_ISSUER in `.env`
|
||||
3. Run `./deploy.sh`
|
||||
4. Change admin password
|
||||
5. Start using!
|
||||
|
||||
For detailed instructions, see `DEPLOYMENT.md`.
|
||||
438
docs/QUICKSTART.md
Normal file
438
docs/QUICKSTART.md
Normal file
@ -0,0 +1,438 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get your application integrated with this OIDC provider in 10 minutes.
|
||||
|
||||
---
|
||||
|
||||
## For the Impatient
|
||||
|
||||
```bash
|
||||
# 1. Get credentials from admin panel
|
||||
https://your-idp.com/admin/login
|
||||
|
||||
# 2. Add to your app (Python example)
|
||||
pip install authlib flask
|
||||
|
||||
# 3. Copy this code
|
||||
# (see Python example below)
|
||||
|
||||
# 4. Done! Users can now log in via OIDC
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- ✅ OIDC Provider deployed and accessible
|
||||
- ✅ Admin access to register your client
|
||||
- ✅ A web application with a backend (Node.js, Python, PHP, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Register Your Application (2 minutes)
|
||||
|
||||
### Via Admin Panel
|
||||
|
||||
1. **Navigate** to: `https://your-idp.com/admin/login`
|
||||
2. **Login** with admin credentials
|
||||
3. **Go to** "Clients" → "Create New Client"
|
||||
4. **Fill in**:
|
||||
- Client Name: `My App`
|
||||
- Redirect URIs: `http://localhost:3000/callback` (one per line)
|
||||
- Allowed Scopes: `openid, profile, email`
|
||||
5. **Click** "Create"
|
||||
6. **Copy** your credentials:
|
||||
```
|
||||
Client ID: abc123def456
|
||||
Client Secret: xyz789... (⚠️ save this - shown only once!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Choose Your Integration Method (1 minute)
|
||||
|
||||
Pick the method that matches your tech stack:
|
||||
|
||||
| If you use... | Go to |
|
||||
|---------------|-------|
|
||||
| Python + Flask | [Python Example](#python-flask) |
|
||||
| Node.js + Express | [Node.js Example](#nodejs-express) |
|
||||
| PHP + Laravel | [PHP Example](#php-laravel) |
|
||||
| Any other | [Generic HTTP Flow](#generic-http-flow) |
|
||||
|
||||
---
|
||||
|
||||
## Python (Flask)
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
pip install flask authlib requests
|
||||
```
|
||||
|
||||
### Code (`app.py`)
|
||||
|
||||
```python
|
||||
from flask import Flask, redirect, url_for, session, jsonify
|
||||
from authlib.integrations.flask_client import OAuth
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.urandom(24)
|
||||
|
||||
# Configure OIDC
|
||||
oauth = OAuth(app)
|
||||
oauth.register(
|
||||
name='myidp',
|
||||
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('/')
|
||||
def index():
|
||||
user = session.get('user')
|
||||
if user:
|
||||
return jsonify(user)
|
||||
return '<a href="/login">Login with OIDC</a>'
|
||||
|
||||
@app.route('/login')
|
||||
def login():
|
||||
redirect_uri = url_for('callback', _external=True)
|
||||
return oauth.myidp.authorize_redirect(redirect_uri)
|
||||
|
||||
@app.route('/callback')
|
||||
def callback():
|
||||
token = oauth.myidp.authorize_access_token()
|
||||
session['user'] = token['userinfo']
|
||||
return redirect('/')
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('user', None)
|
||||
return redirect('/')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=3000, debug=True)
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
# Open http://localhost:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Node.js (Express)
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install express express-session passport openid-client
|
||||
```
|
||||
|
||||
### Code (`server.js`)
|
||||
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const passport = require('passport');
|
||||
const { Issuer, Strategy } = require('openid-client');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(session({
|
||||
secret: 'keyboard cat',
|
||||
resave: false,
|
||||
saveUninitialized: true
|
||||
}));
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
passport.serializeUser((user, done) => done(null, user));
|
||||
passport.deserializeUser((user, done) => done(null, user));
|
||||
|
||||
// Discover and configure OIDC
|
||||
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);
|
||||
}));
|
||||
|
||||
// Routes
|
||||
app.get('/', (req, res) => {
|
||||
if (req.isAuthenticated()) {
|
||||
res.send(`<h1>Hello, ${req.user.name}!</h1><a href="/logout">Logout</a>`);
|
||||
} else {
|
||||
res.send('<a href="/login">Login with OIDC</a>');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/login', passport.authenticate('oidc'));
|
||||
|
||||
app.get('/callback',
|
||||
passport.authenticate('oidc', { failureRedirect: '/' }),
|
||||
(req, res) => res.redirect('/')
|
||||
);
|
||||
|
||||
app.get('/logout', (req, res) => {
|
||||
req.logout(() => res.redirect('/'));
|
||||
});
|
||||
|
||||
app.listen(3000, () => console.log('App running on http://localhost:3000'));
|
||||
});
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
node server.js
|
||||
# Open http://localhost:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PHP (Laravel)
|
||||
|
||||
### Install Socialite
|
||||
|
||||
```bash
|
||||
composer require laravel/socialite
|
||||
composer require socialiteproviders/oidc
|
||||
```
|
||||
|
||||
### Configure (`config/services.php`)
|
||||
|
||||
```php
|
||||
'oidc' => [
|
||||
'client_id' => env('OIDC_CLIENT_ID'),
|
||||
'client_secret' => env('OIDC_CLIENT_SECRET'),
|
||||
'redirect' => env('OIDC_REDIRECT_URI'),
|
||||
'base_url' => env('OIDC_ISSUER'),
|
||||
],
|
||||
```
|
||||
|
||||
### Environment (`.env`)
|
||||
|
||||
```bash
|
||||
OIDC_CLIENT_ID=YOUR_CLIENT_ID
|
||||
OIDC_CLIENT_SECRET=YOUR_CLIENT_SECRET
|
||||
OIDC_REDIRECT_URI=http://localhost:8000/callback
|
||||
OIDC_ISSUER=https://your-idp.com
|
||||
```
|
||||
|
||||
### Routes (`routes/web.php`)
|
||||
|
||||
```php
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
Route::get('/login', function () {
|
||||
return Socialite::driver('oidc')->redirect();
|
||||
});
|
||||
|
||||
Route::get('/callback', function () {
|
||||
$user = Socialite::driver('oidc')->user();
|
||||
|
||||
// Find or create user in database
|
||||
$localUser = User::updateOrCreate(
|
||||
['email' => $user->email],
|
||||
['name' => $user->name]
|
||||
);
|
||||
|
||||
Auth::login($localUser);
|
||||
|
||||
return redirect('/dashboard');
|
||||
});
|
||||
|
||||
Route::get('/logout', function () {
|
||||
Auth::logout();
|
||||
return redirect('/');
|
||||
});
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
php artisan serve
|
||||
# Open http://localhost:8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Generic HTTP Flow
|
||||
|
||||
If you can't use a library, here's the manual flow:
|
||||
|
||||
### Step 1: Redirect to Authorization Endpoint
|
||||
|
||||
```http
|
||||
GET https://your-idp.com/authorize?
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
redirect_uri=http://localhost:3000/callback&
|
||||
response_type=code&
|
||||
scope=openid%20profile%20email&
|
||||
state=RANDOM_STATE_TOKEN
|
||||
```
|
||||
|
||||
### Step 2: Handle Callback
|
||||
|
||||
User is redirected back with a code:
|
||||
```
|
||||
http://localhost:3000/callback?code=ABC123&state=RANDOM_STATE_TOKEN
|
||||
```
|
||||
|
||||
**Verify state token** to prevent CSRF!
|
||||
|
||||
### Step 3: 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:3000/callback" \
|
||||
-d "client_id=YOUR_CLIENT_ID" \
|
||||
-d "client_secret=YOUR_CLIENT_SECRET"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGci...",
|
||||
"id_token": "eyJhbGci...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Get User Info
|
||||
|
||||
```bash
|
||||
curl https://your-idp.com/userinfo \
|
||||
-H "Authorization: Bearer ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"sub": "user-123",
|
||||
"email": "john@example.com",
|
||||
"name": "John Doe",
|
||||
"preferred_username": "john"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Integration
|
||||
|
||||
### 1. Start Your App
|
||||
|
||||
```bash
|
||||
# Your app should now be running on localhost
|
||||
```
|
||||
|
||||
### 2. Click "Login"
|
||||
|
||||
Navigate to your app's login link. You should be redirected to:
|
||||
```
|
||||
https://your-idp.com/authorize?client_id=...
|
||||
```
|
||||
|
||||
### 3. Login
|
||||
|
||||
Use test credentials:
|
||||
```
|
||||
Username: test
|
||||
Password: test123
|
||||
```
|
||||
|
||||
### 4. Verify
|
||||
|
||||
After login, you should:
|
||||
- ✅ Be redirected back to your app
|
||||
- ✅ See user information
|
||||
- ✅ Have an active session
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
### "Invalid Redirect URI"
|
||||
|
||||
**Problem**: Redirect URI doesn't match.
|
||||
|
||||
**Fix**:
|
||||
1. Check exact match (including trailing slash)
|
||||
2. Update in admin panel if needed
|
||||
|
||||
### "Invalid Client"
|
||||
|
||||
**Problem**: Wrong client ID or secret.
|
||||
|
||||
**Fix**:
|
||||
1. Double-check credentials
|
||||
2. No extra spaces or line breaks
|
||||
|
||||
### "Connection Refused"
|
||||
|
||||
**Problem**: OIDC provider not accessible.
|
||||
|
||||
**Fix**:
|
||||
1. Verify provider is running: `curl https://your-idp.com/health`
|
||||
2. Check network/firewall
|
||||
|
||||
### CORS Errors (for SPAs)
|
||||
|
||||
**Problem**: Browser blocks cross-origin requests.
|
||||
|
||||
**Solution**: Don't call OIDC endpoints from frontend. Use a backend proxy.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once basic login works:
|
||||
|
||||
1. **Add User Persistence**: Store user in your database
|
||||
2. **Handle Logout**: Clear session and optionally redirect to IdP logout
|
||||
3. **Refresh Tokens**: Implement token refresh (when available)
|
||||
4. **Error Handling**: Add proper error pages
|
||||
5. **Production Setup**: Use HTTPS, secure cookies
|
||||
|
||||
---
|
||||
|
||||
## Complete Examples
|
||||
|
||||
Check out complete example applications:
|
||||
|
||||
- **Python Flask**: `examples/python-flask/` (coming soon)
|
||||
- **Node.js Express**: `examples/nodejs-express/` (coming soon)
|
||||
- **PHP Laravel**: `examples/php-laravel/` (coming soon)
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- 📖 [Full API Guide](API_GUIDE.md)
|
||||
- 🏗️ [Architecture](ARCHITECTURE.md)
|
||||
- 🚀 [Deployment](deployment.md)
|
||||
- 🐛 Report issues on GitHub
|
||||
|
||||
---
|
||||
|
||||
**You're all set!** 🎉
|
||||
|
||||
Your users can now log in via OIDC in just a few clicks.
|
||||
60
docs/TESTING.md
Normal file
60
docs/TESTING.md
Normal file
@ -0,0 +1,60 @@
|
||||
# Testing Guide
|
||||
|
||||
This document outlines the current testing strategy for the OIDC server and provides instructions on how to perform tests.
|
||||
|
||||
## Overview
|
||||
|
||||
Currently, the project relies on manual testing using a simple Flask-based OIDC client application (`test_client.py`). This test client is designed to simulate a real-world application and allows you to walk through the entire OIDC Authorization Code Flow.
|
||||
|
||||
There is not yet a suite of automated unit or integration tests. Adding a formal testing framework like PyTest is a key goal for future development (see `TODO.md`).
|
||||
|
||||
## Running the Test Client
|
||||
|
||||
The test client is a separate Flask application that runs on port `8080`. To use it, you need to have both the main OIDC server and the test client running at the same time.
|
||||
|
||||
### Step 1: Run the OIDC Server
|
||||
|
||||
In one terminal, start the main OIDC server (either with Docker or locally). For testing, it's easiest to run it locally:
|
||||
|
||||
```bash
|
||||
# In your first terminal
|
||||
export FLASK_APP=oidc_server.py
|
||||
export FLASK_ENV=development
|
||||
|
||||
# Make sure your database is up-to-date
|
||||
flask db upgrade
|
||||
flask seed
|
||||
|
||||
# Run the OIDC server (defaults to port 5000)
|
||||
flask run
|
||||
```
|
||||
|
||||
### Step 2: Run the Test Client
|
||||
|
||||
The test client is pre-configured to work with the default settings of the OIDC server running on `localhost:5000`.
|
||||
|
||||
In a second terminal, run the `test_client.py` application:
|
||||
|
||||
```bash
|
||||
# In your second terminal
|
||||
python3 test_client.py
|
||||
```
|
||||
|
||||
This will start the test client on `http://localhost:8080`.
|
||||
|
||||
### Step 3: Perform the Test
|
||||
|
||||
1. **Open your browser** and navigate to the test client's URL: `http://localhost:8080`.
|
||||
2. **Click the "Mit OIDC einloggen" button.** This will redirect you to the OIDC server's login page.
|
||||
3. **Log in** with one of the test user accounts (e.g., `test` / `test123`).
|
||||
4. **Successful Login**: After a successful login, the OIDC server will redirect you back to the test client's callback URL (`/callback`).
|
||||
5. **Token Exchange**: The test client will automatically exchange the received authorization code for an access token and an ID token.
|
||||
6. **View Results**: The test client's homepage will now display the user information retrieved from the `/userinfo` endpoint, as well as the contents of the access token and the ID token.
|
||||
|
||||
This process allows you to manually verify that the entire OIDC flow is working as expected.
|
||||
|
||||
## Future Improvements
|
||||
|
||||
- **Automated Integration Tests**: The `test_client.py` could be extended to make automated requests and assertions instead of requiring manual browser interaction.
|
||||
- **Unit Tests**: A suite of unit tests should be created to test individual functions and components in isolation (e.g., model logic, specific OIDC validation rules).
|
||||
- **PyTest Framework**: The project should adopt the PyTest framework for writing and running tests in a structured way.
|
||||
210
docs/TODO.md
Normal file
210
docs/TODO.md
Normal file
@ -0,0 +1,210 @@
|
||||
# OIDC Server - TODO & Roadmap
|
||||
|
||||
## ✅ Bereits implementiert
|
||||
|
||||
### Core OIDC Funktionalität
|
||||
- ✅ Authorization Code Flow (vollständig implementiert)
|
||||
- ✅ Discovery Endpoint (`/.well-known/openid-configuration`)
|
||||
- ✅ `/authorize` - Authorization Endpoint
|
||||
- ✅ `/token` - Token Exchange
|
||||
- ✅ `/userinfo` - User Info Endpoint
|
||||
- ✅ JWT ID Tokens (signiert mit RS256)
|
||||
- ✅ Access Tokens mit Validation
|
||||
- ✅ Authorization Codes mit TTL
|
||||
|
||||
### User Management
|
||||
- ✅ User Registration (Self-Service)
|
||||
- ✅ Password Change (Self-Service)
|
||||
- ✅ Bcrypt Password Hashing
|
||||
- ✅ User Login mit Session
|
||||
- ✅ User Dashboard
|
||||
|
||||
### Admin Features
|
||||
- ✅ Admin Login (separate Session)
|
||||
- ✅ CRUD für User-Verwaltung
|
||||
- ✅ Rollen-System (user, admin, moderator, readonly)
|
||||
- ✅ Permissions-System (JSON Array, comma-separated UI)
|
||||
- ✅ User Activate/Deactivate
|
||||
- ✅ **(NEU)** Multi-Client Support - OIDC-Clients können über das Admin-Panel verwaltet werden.
|
||||
|
||||
### Security & Data
|
||||
- ✅ SQLite Database (PostgreSQL-ready)
|
||||
- ✅ Session-based Authentication
|
||||
- ✅ CSRF Protection (state parameter)
|
||||
- ✅ Sichere Landing Page (keine Secrets exposed)
|
||||
- ✅ **(NEU)** Rate Limiting - Schutz vor Brute-Force auf Login/Token Endpoints
|
||||
- ✅ **(NEU)** Audit Logging - Loggt Logins und Admin-Aktionen in die Datenbank
|
||||
- ✅ **(NEU)** Asymmetric Token Signing (RS256) - ID Tokens werden mit RS256 signiert und der Public Key per `/jwks` Endpoint bereitgestellt.
|
||||
|
||||
### DevOps/Production
|
||||
- ✅ **(NEU)** Environment Configuration - `.env` File Support für Secrets (Development/Production)
|
||||
- ✅ **(NEU)** Health Check Endpoint - `/health` für Monitoring und Load Balancer
|
||||
- ✅ **(NEU)** Docker Support - `Dockerfile` und `docker-compose.yml` für einfaches Deployment
|
||||
- ✅ **(NEU)** Production WSGI Server - Gunicorn wird im Docker Container verwendet
|
||||
- ✅ **(NEU)** Database Migrations - Schema-Änderungen werden mit Flask-Migrate (Alembic) verwaltet.
|
||||
|
||||
### UI/UX
|
||||
- ✅ Modernes Dark Mode Design
|
||||
- ✅ Responsive Layout
|
||||
- ✅ Alle Templates mit Theme Toggle
|
||||
|
||||
---
|
||||
|
||||
## 🚀 TODO - Nächste Features
|
||||
|
||||
### Security Improvements (Priorität: HOCH)
|
||||
|
||||
#### 2. Token Refresh Flow
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Hoch
|
||||
**Beschreibung:**
|
||||
- Refresh Tokens für längere Sessions
|
||||
- User muss nicht alle X Minuten neu einloggen
|
||||
**Tasks:**
|
||||
- [ ] RefreshToken Model in DB erstellen
|
||||
- [ ] `/token` Endpoint erweitern: `grant_type=refresh_token`
|
||||
- [ ] Refresh Token Rotation implementieren
|
||||
- [ ] Token Expiry konfigurierbar machen
|
||||
|
||||
#### 4. HTTPS Enforcement
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Hoch (für Production)
|
||||
**Beschreibung:**
|
||||
- Aktuell nur HTTP (Development)
|
||||
- Production: SSL/TLS zwingend
|
||||
**Tasks:**
|
||||
- [ ] SSL Certificates (Let's Encrypt)
|
||||
- [ ] Nginx/Traefik Reverse Proxy Setup
|
||||
- [ ] HTTPS Redirect erzwingen
|
||||
- [ ] Secure Cookie Flags setzen
|
||||
|
||||
#### 5. PKCE Support
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Mittel
|
||||
**Beschreibung:**
|
||||
- Proof Key for Code Exchange
|
||||
- Wichtig für SPAs und Mobile Apps ohne Client Secret
|
||||
**Tasks:**
|
||||
- [ ] PKCE Parameter in `/authorize` akzeptieren (`code_challenge`, `code_challenge_method`)
|
||||
- [ ] Code Verifier Validation in `/token`
|
||||
- [ ] S256 und plain methods unterstützen
|
||||
|
||||
---
|
||||
|
||||
### Features (Priorität: MITTEL)
|
||||
|
||||
#### 7. Scope Management
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Mittel
|
||||
**Beschreibung:**
|
||||
- Aktuell: Scopes werden akzeptiert aber nicht enforced
|
||||
- Bessere Scope → Permission Mapping
|
||||
**Tasks:**
|
||||
- [ ] Scope Definition System
|
||||
- [ ] Scope Validation gegen User Permissions
|
||||
- [ ] Consent Screen für Scopes
|
||||
- [ ] Scope-basierte Token Claims
|
||||
|
||||
#### 8. Email Verification
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Mittel
|
||||
**Beschreibung:**
|
||||
- Email Verification bei Registration
|
||||
- Password Reset per Email
|
||||
**Tasks:**
|
||||
- [ ] SMTP Konfiguration
|
||||
- [ ] Email Verification Token System
|
||||
- [ ] Email Templates (Verification, Password Reset)
|
||||
- [ ] `/verify-email` und `/reset-password` Endpoints
|
||||
|
||||
#### 9. 2FA/MFA
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Niedrig
|
||||
**Beschreibung:**
|
||||
- Two-Factor Authentication
|
||||
**Tasks:**
|
||||
- [ ] TOTP Support (Google Authenticator, Authy)
|
||||
- [ ] QR Code Generation für TOTP Setup
|
||||
- [ ] Backup Codes generieren
|
||||
- [ ] 2FA Enforcement für Admin Accounts
|
||||
|
||||
---
|
||||
|
||||
### Admin Features (Priorität: MITTEL)
|
||||
|
||||
#### 12. Token Management
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Niedrig
|
||||
**Beschreibung:**
|
||||
- Aktive Tokens anzeigen und verwalten
|
||||
**Tasks:**
|
||||
- [ ] Token List View (Access + Refresh Tokens)
|
||||
- [ ] Token Revocation UI
|
||||
- [ ] Token Lifetime Configuration
|
||||
- [ ] "Revoke all tokens for user" Funktion
|
||||
|
||||
#### 13. Bulk Operations
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Niedrig
|
||||
**Beschreibung:**
|
||||
- Bulk User Import/Management
|
||||
**Tasks:**.
|
||||
- [ ] CSV Import für Users
|
||||
- [ ] Bulk Permission Assignment
|
||||
- [ ] User Groups erstellen
|
||||
- [ ] Group-based Permissions
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
### User Experience (Priorität: NIEDRIG)
|
||||
|
||||
#### 19. Consent Screen
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Niedrig
|
||||
**Beschreibung:**
|
||||
- User muss Scopes bestätigen
|
||||
- "Diese App möchte Zugriff auf..."
|
||||
**Tasks:**
|
||||
- [ ] Consent Screen Template
|
||||
- [ ] Scope Descriptions
|
||||
- [ ] Remember Consent per Client
|
||||
- [ ] Revoke Consent UI
|
||||
|
||||
#### 20. Session Management für User
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Niedrig
|
||||
**Beschreibung:**
|
||||
- Aktive Sessions anzeigen
|
||||
**Tasks:**
|
||||
- [ ] Session List View
|
||||
- [ ] "Logout from all devices"
|
||||
- [ ] Session Details (IP, Location, Device)
|
||||
- [ ] Suspicious Login Warnings
|
||||
|
||||
#### 21. Internationalization (i18n)
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Niedrig
|
||||
**Beschreibung:**
|
||||
- Mehrsprachige UI
|
||||
- Aktuell: Mix aus Deutsch/Englisch
|
||||
**Tasks:**
|
||||
- [ ] Flask-Babel Integration
|
||||
- [ ] Deutsche Übersetzungen
|
||||
- [ ] Englische Übersetzungen
|
||||
- [ ] Language Switcher in UI
|
||||
|
||||
#### 22. Profile Picture Support
|
||||
**Status:** ⏳ Offen
|
||||
**Priorität:** Niedrig
|
||||
**Beschreibung:**
|
||||
- Avatar Upload
|
||||
**Tasks:**
|
||||
- [ ] Avatar Upload im User Dashboard
|
||||
- [ ] Image Resizing/Cropping
|
||||
- [ ] Gravatar Fallback
|
||||
- [ ] Avatar in ID Token (picture claim)
|
||||
|
||||
---
|
||||
|
||||
**Letzte Aktualisierung:** 2025-11-27
|
||||
566
docs/deployment.md
Normal file
566
docs/deployment.md
Normal file
@ -0,0 +1,566 @@
|
||||
# Deployment Guide
|
||||
|
||||
Complete guide for deploying the OIDC Identity Provider in both development and production environments.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Development Deployment](#development-deployment)
|
||||
2. [Production Deployment](#production-deployment)
|
||||
3. [Management Commands](#management-commands)
|
||||
4. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Development Deployment
|
||||
|
||||
### Quick Start
|
||||
|
||||
The fastest way to get the OIDC server running for development and testing.
|
||||
|
||||
#### Step 1: Get the Code
|
||||
|
||||
```bash
|
||||
git clone <your-repo-url> wlkns_auth
|
||||
cd wlkns_auth
|
||||
```
|
||||
|
||||
#### Step 2: Configure Environment
|
||||
|
||||
```bash
|
||||
# Copy the production environment template
|
||||
cp .env.production .env
|
||||
|
||||
# Edit if needed (default works for local development)
|
||||
nano .env
|
||||
```
|
||||
|
||||
For local development, the default `OIDC_ISSUER=http://localhost:5000` works fine.
|
||||
|
||||
#### Step 3: Deploy with Docker
|
||||
|
||||
```bash
|
||||
# Build and start services
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# Or use the deployment script
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
#### Step 4: Verify Deployment
|
||||
|
||||
**Check service status:**
|
||||
```bash
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
```
|
||||
|
||||
**Test health endpoint:**
|
||||
```bash
|
||||
curl http://localhost:5000/health
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"database": "healthy",
|
||||
"timestamp": "2025-11-27T09:19:54.558214",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
### Access Information
|
||||
|
||||
**OIDC Server:**
|
||||
- Base URL: http://localhost:5000
|
||||
- Discovery: http://localhost:5000/.well-known/openid-configuration
|
||||
- Admin Panel: http://localhost:5000/admin/login
|
||||
|
||||
**Default Credentials:**
|
||||
|
||||
*Admin User:*
|
||||
- Username: `admin`
|
||||
- Password: `admin123`
|
||||
- Role: admin
|
||||
- Permissions: read:data, write:data, manage:users, manage:settings
|
||||
|
||||
*Test User:*
|
||||
- Username: `test`
|
||||
- Password: `test123`
|
||||
- Role: user
|
||||
- Permissions: read:data
|
||||
|
||||
**⚠️ IMPORTANT:** Change the admin password immediately after first login!
|
||||
|
||||
**Default OIDC Client:**
|
||||
- Client ID: `test-client`
|
||||
- Client Secret: (generated in `.env`)
|
||||
- Redirect URIs: `http://localhost:8080/callback`
|
||||
- Allowed Scopes: openid, profile, email
|
||||
|
||||
### Development Testing
|
||||
|
||||
**1. Test OIDC Discovery:**
|
||||
```bash
|
||||
curl http://localhost:5000/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
**2. Test Admin Login:**
|
||||
1. Open http://localhost:5000/admin/login in browser
|
||||
2. Login with `admin` / `admin123`
|
||||
3. You should see the admin dashboard
|
||||
|
||||
**3. Test User Registration:**
|
||||
1. Open http://localhost:5000/register
|
||||
2. Create a new user account
|
||||
3. Login at http://localhost:5000/login
|
||||
|
||||
**4. Test OIDC Flow (Optional):**
|
||||
```bash
|
||||
# In a separate terminal
|
||||
python3 test_client.py
|
||||
```
|
||||
Then open http://localhost:8080 and click "Mit OIDC einloggen"
|
||||
|
||||
---
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- A server with Docker and Docker Compose installed
|
||||
- A domain name pointing to your server's IP address
|
||||
- Basic familiarity with the command line
|
||||
- Ports 80 and 443 open if exposing to the internet
|
||||
|
||||
### Step 1: Get the Code
|
||||
|
||||
```bash
|
||||
git clone <your-repo-url> wlkns_auth
|
||||
cd wlkns_auth
|
||||
```
|
||||
|
||||
### Step 2: Configure the Environment
|
||||
|
||||
Copy the production environment template:
|
||||
|
||||
```bash
|
||||
cp .env.production .env
|
||||
```
|
||||
|
||||
**Required: Set your public domain:**
|
||||
|
||||
```bash
|
||||
nano .env
|
||||
```
|
||||
|
||||
Change the `OIDC_ISSUER` to your server's public URL:
|
||||
```bash
|
||||
# Example: OIDC_ISSUER=https://auth.yourdomain.com
|
||||
OIDC_ISSUER=https://auth.example.com
|
||||
```
|
||||
|
||||
**Security Checklist:**
|
||||
- ✅ Generate new `SECRET_KEY` (done automatically in `.env.production`)
|
||||
- ✅ Use strong `POSTGRES_PASSWORD` (done automatically)
|
||||
- ✅ Set proper `OIDC_ISSUER` with your domain
|
||||
- ✅ Review token lifetimes (`ACCESS_TOKEN_LIFETIME`, etc.)
|
||||
|
||||
### Step 3: Deploy the Application
|
||||
|
||||
Run the deployment script:
|
||||
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Build Docker images
|
||||
2. Start OIDC server and PostgreSQL
|
||||
3. Apply database migrations
|
||||
4. Seed initial data
|
||||
|
||||
### Step 4: Secure the Admin Account
|
||||
|
||||
**CRITICAL:** Change the default admin password immediately!
|
||||
|
||||
1. Navigate to `https://<your-domain>/admin/login`
|
||||
2. Log in with default credentials:
|
||||
- Username: `admin`
|
||||
- Password: `admin123`
|
||||
3. Go to user list → Edit admin user → Set a strong password
|
||||
|
||||
### Advanced Production Scenarios
|
||||
|
||||
#### Scenario A: Using an Existing Nginx Reverse Proxy
|
||||
|
||||
If you already have Nginx running and want it to manage SSL:
|
||||
|
||||
1. **Deploy the OIDC Server** following Steps 1-3 above
|
||||
|
||||
2. **Configure Nginx** with this server block:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name auth.yourdomain.com;
|
||||
|
||||
# Your SSL certificate configuration
|
||||
ssl_certificate /path/to/your/fullchain.pem;
|
||||
ssl_certificate_key /path/to/your/privkey.pem;
|
||||
|
||||
# SSL hardening (recommended)
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Test and Reload Nginx:**
|
||||
|
||||
```bash
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
#### Scenario B: Manual Deployment (Without Docker)
|
||||
|
||||
While Docker is recommended, you can run the application manually:
|
||||
|
||||
**1. Install Dependencies:**
|
||||
```bash
|
||||
# Create virtual environment
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**2. Configure Environment:**
|
||||
Create a `.env` file with these required variables:
|
||||
```bash
|
||||
FLASK_APP=oidc_server.py
|
||||
FLASK_ENV=production
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/oidc_db
|
||||
OIDC_ISSUER=https://auth.yourdomain.com
|
||||
SECRET_KEY=<generate-random-secret>
|
||||
```
|
||||
|
||||
**3. Run Database Migrations:**
|
||||
```bash
|
||||
flask db upgrade
|
||||
flask seed
|
||||
```
|
||||
|
||||
**4. Start the Server:**
|
||||
```bash
|
||||
# For production, use Gunicorn
|
||||
gunicorn --bind 0.0.0.0:5000 "oidc_server:app"
|
||||
|
||||
# Or with workers
|
||||
gunicorn --workers 4 --bind 0.0.0.0:5000 "oidc_server:app"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Management Commands
|
||||
|
||||
### Service Control
|
||||
|
||||
```bash
|
||||
# View logs (all services)
|
||||
docker-compose -f docker-compose.prod.yml logs -f
|
||||
|
||||
# View logs (specific service)
|
||||
docker-compose -f docker-compose.prod.yml logs -f oidc_server
|
||||
docker-compose -f docker-compose.prod.yml logs -f postgres
|
||||
|
||||
# Stop services
|
||||
docker-compose -f docker-compose.prod.yml down
|
||||
|
||||
# Start services
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# Restart services
|
||||
docker-compose -f docker-compose.prod.yml restart
|
||||
|
||||
# Rebuild and restart
|
||||
docker-compose -f docker-compose.prod.yml up -d --build
|
||||
```
|
||||
|
||||
### Database Operations
|
||||
|
||||
**Run Migrations:**
|
||||
```bash
|
||||
docker exec -e FLASK_APP=oidc_server.py oidc_server flask db upgrade
|
||||
```
|
||||
|
||||
**Seed Database:**
|
||||
```bash
|
||||
docker exec -e FLASK_APP=oidc_server.py oidc_server flask seed
|
||||
```
|
||||
|
||||
**Access PostgreSQL:**
|
||||
```bash
|
||||
docker exec -it oidc_postgres psql -U oidc_user -d oidc_db
|
||||
```
|
||||
|
||||
**Query Users:**
|
||||
```bash
|
||||
docker exec oidc_postgres psql -U oidc_user -d oidc_db -c "SELECT username, email, is_admin, role FROM users;"
|
||||
```
|
||||
|
||||
**Create Database Backup:**
|
||||
```bash
|
||||
# Create backup directory
|
||||
mkdir -p backups
|
||||
|
||||
# Create compressed backup
|
||||
docker exec oidc_postgres pg_dump -U oidc_user -d oidc_db | gzip > backups/oidc_backup_$(date +%Y%m%d_%H%M%S).sql.gz
|
||||
```
|
||||
|
||||
**Restore from Backup:**
|
||||
```bash
|
||||
# Stop the application
|
||||
docker-compose -f docker-compose.prod.yml down
|
||||
|
||||
# Start only PostgreSQL
|
||||
docker-compose -f docker-compose.prod.yml up -d postgres
|
||||
|
||||
# Restore backup
|
||||
gunzip -c backups/oidc_backup_20251127_120000.sql.gz | docker exec -i oidc_postgres psql -U oidc_user -d oidc_db
|
||||
|
||||
# Start all services
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### Updating the Application
|
||||
|
||||
To update to the latest version:
|
||||
|
||||
```bash
|
||||
# Pull latest code
|
||||
git pull
|
||||
|
||||
# Rebuild and restart
|
||||
docker-compose -f docker-compose.prod.yml up -d --build
|
||||
|
||||
# Apply any new migrations
|
||||
docker exec -e FLASK_APP=oidc_server.py oidc_server flask db upgrade
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Services Won't Start
|
||||
|
||||
```bash
|
||||
# Check logs for errors
|
||||
docker-compose -f docker-compose.prod.yml logs
|
||||
|
||||
# Check if ports are already in use
|
||||
sudo netstat -tlnp | grep 5000
|
||||
sudo netstat -tlnp | grep 5432
|
||||
|
||||
# Check Docker service status
|
||||
sudo systemctl status docker
|
||||
```
|
||||
|
||||
### Database Connection Issues
|
||||
|
||||
```bash
|
||||
# Verify PostgreSQL is healthy
|
||||
docker-compose -f docker-compose.prod.yml ps postgres
|
||||
|
||||
# Test database connection
|
||||
docker exec oidc_postgres pg_isready -U oidc_user -d oidc_db
|
||||
|
||||
# Check database logs
|
||||
docker-compose -f docker-compose.prod.yml logs postgres
|
||||
```
|
||||
|
||||
### "Bad Gateway" from Nginx
|
||||
|
||||
This usually means the OIDC server container is not running:
|
||||
|
||||
```bash
|
||||
# Check container status
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
|
||||
# View OIDC server logs
|
||||
docker-compose -f docker-compose.prod.yml logs oidc_server
|
||||
|
||||
# Restart the service
|
||||
docker-compose -f docker-compose.prod.yml restart oidc_server
|
||||
```
|
||||
|
||||
### "Invalid Credentials" on Login
|
||||
|
||||
```bash
|
||||
# Ensure database was seeded
|
||||
docker exec -e FLASK_APP=oidc_server.py oidc_server flask seed
|
||||
|
||||
# Check if users exist
|
||||
docker exec oidc_postgres psql -U oidc_user -d oidc_db -c "SELECT * FROM users;"
|
||||
|
||||
# Verify default password hasn't been changed
|
||||
```
|
||||
|
||||
### Container Keeps Restarting
|
||||
|
||||
```bash
|
||||
# Check container logs for errors
|
||||
docker logs oidc_server --tail=100
|
||||
|
||||
# Common issues:
|
||||
# - Missing environment variables
|
||||
# - Database connection failure
|
||||
# - Syntax errors in Python files
|
||||
# - Missing JWT keys
|
||||
|
||||
# Check environment variables
|
||||
docker exec oidc_server env | grep FLASK
|
||||
```
|
||||
|
||||
### "Invalid Redirect URI" Error
|
||||
|
||||
Make sure the `redirect_uri` your client application is using is listed in that client's configuration in the admin dashboard.
|
||||
|
||||
```bash
|
||||
# Check client configuration
|
||||
docker exec oidc_postgres psql -U oidc_user -d oidc_db -c "SELECT client_id, redirect_uris FROM clients;"
|
||||
```
|
||||
|
||||
### "Invalid Client ID" Error
|
||||
|
||||
Ensure the `client_id` is correct and the client exists:
|
||||
|
||||
```bash
|
||||
# List all clients
|
||||
docker exec oidc_postgres psql -U oidc_user -d oidc_db -c "SELECT * FROM clients;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## OIDC Endpoints Reference
|
||||
|
||||
### Discovery Document
|
||||
```
|
||||
GET http://localhost:5000/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
### Authorization Endpoint
|
||||
```
|
||||
GET http://localhost:5000/authorize
|
||||
```
|
||||
Parameters:
|
||||
- `client_id`: Client identifier
|
||||
- `redirect_uri`: Callback URL
|
||||
- `response_type`: `code`
|
||||
- `scope`: `openid profile email`
|
||||
- `state`: CSRF protection token
|
||||
|
||||
### Token Endpoint
|
||||
```
|
||||
POST http://localhost:5000/token
|
||||
```
|
||||
Parameters:
|
||||
- `grant_type`: `authorization_code`
|
||||
- `code`: Authorization code
|
||||
- `redirect_uri`: Same as authorization
|
||||
- `client_id`: Client identifier
|
||||
- `client_secret`: Client secret
|
||||
|
||||
### UserInfo Endpoint
|
||||
```
|
||||
GET http://localhost:5000/userinfo
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
### Health Check
|
||||
```
|
||||
GET http://localhost:5000/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Checklist
|
||||
|
||||
### Production Security
|
||||
|
||||
- ✅ Change default admin password
|
||||
- ✅ Use HTTPS (via Nginx reverse proxy)
|
||||
- ✅ Set strong `SECRET_KEY`
|
||||
- ✅ Use strong database passwords
|
||||
- ✅ Enable firewall (only expose 80/443)
|
||||
- ✅ Regular database backups
|
||||
- ✅ Keep Docker images updated
|
||||
- ✅ Review audit logs regularly
|
||||
- ✅ Configure rate limiting appropriately
|
||||
- ✅ Use environment variables for secrets
|
||||
|
||||
### Active Security Features
|
||||
|
||||
- bcrypt password hashing
|
||||
- RS256 JWT signing
|
||||
- Session security (HttpOnly, SameSite)
|
||||
- Rate limiting on login endpoints
|
||||
- Audit logging
|
||||
- Non-root Docker user
|
||||
- Strong generated secrets
|
||||
|
||||
---
|
||||
|
||||
## What's Working
|
||||
|
||||
✅ Docker Compose deployment
|
||||
✅ PostgreSQL database with persistence
|
||||
✅ User authentication (bcrypt hashing)
|
||||
✅ Admin panel with CRUD operations
|
||||
✅ OIDC discovery endpoint
|
||||
✅ Authorization endpoint
|
||||
✅ Token endpoint
|
||||
✅ UserInfo endpoint
|
||||
✅ Health check endpoint
|
||||
✅ Rate limiting
|
||||
✅ Audit logging
|
||||
✅ Database migrations
|
||||
✅ Multi-client support
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Immediate Actions
|
||||
|
||||
1. Change default admin password
|
||||
2. Set up HTTPS with reverse proxy
|
||||
3. Configure automated database backups
|
||||
4. Set up monitoring and log aggregation
|
||||
|
||||
### Optional Improvements
|
||||
|
||||
1. Fix JWKS endpoint (known issue with public key format)
|
||||
2. Implement refresh tokens
|
||||
3. Add PKCE support for public clients
|
||||
4. Add email verification
|
||||
5. Implement 2FA/MFA
|
||||
6. Set up automated testing
|
||||
|
||||
---
|
||||
|
||||
For more information, see:
|
||||
- [Architecture Documentation](architecture.md)
|
||||
- [Testing Guide](testing.md)
|
||||
- [Project Roadmap](todo.md)
|
||||
1264
docs/guides/python-quick-start-guide.md
Normal file
1264
docs/guides/python-quick-start-guide.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user