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

79
docs/ARCHITECTURE.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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)

File diff suppressed because it is too large Load Diff