80 lines
5.4 KiB
Markdown
80 lines
5.4 KiB
Markdown
# 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.
|