Files
oicd/docs/ARCHITECTURE.md
2025-11-30 00:07:24 +01:00

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 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.