5.4 KiB
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 baseConfigclass. It reads values from the environment (populated by the.envfile) and sets sane defaults.oidc_server.py: At startup, the main application file reads theFLASK_ENVenvironment variable to determine which configuration class to load fromconfig.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:
-
Initialization:
- Creating the Flask
appinstance. - Loading the correct configuration object.
- Initializing the database connection (
db.init_app(app)). - Initializing
Flask-Migratefor database schema management. - Initializing
Flask-Limiterfor rate limiting.
- Creating the Flask
-
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_requireddecorator.
- OIDC Endpoints: Standard endpoints required by the OpenID Connect specification (
-
CLI Commands: The application defines custom
flaskcommands, 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 (usingbcrypt), role, and a flexible JSON-based permissions list.Client: Stores information about OIDC client applications. Each client has aclient_id, a hashedclient_secret, a list of allowedredirect_uris, and a list ofallowed_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/userinfoendpoint. 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.
-
/authorize(GET):- A client application redirects the user to this endpoint.
- The server validates the
client_idandredirect_uriagainst theClienttable in the database. - It stores the authorization request parameters in the user's session and displays a login page.
-
/authorize(POST):- The user submits their credentials.
- The server validates the username and password against the
Usertable. - On success, it generates a new
AuthorizationCode, saves it to the database, and redirects the user back to the client'sredirect_uriwith the code included as a query parameter.
-
/token(POST):- The client application makes a direct, back-channel request to this endpoint, sending the authorization code along with its
client_idandclient_secret. - The server validates the client's credentials and the authorization code.
- It marks the authorization code as used.
- It generates a new
AccessTokenand anid_token(a JWT signed with theRS256algorithm). - It returns the tokens to the client in a JSON response.
- The client application makes a direct, back-channel request to this endpoint, sending the authorization code along with its
-
/userinfo(GET):- The client can use the
AccessTokento request information about the user from this endpoint. - The server validates the access token and returns the user's claims (e.g., name, email).
- The client can use the
This flow ensures that the user's credentials are never exposed to the client application and that tokens are securely issued and validated.