python-oidc is a lightweight, asynchronous (ASGI) OpenID Connect (OIDC) authentication middleware built with FastAPI. It aims to provide a flexible and robust backend for authenticating users against various Identity Providers (IdPs) like Keycloak, Google, Okta, and more, handling the OAuth 2.0/OIDC dance and providing session management.
Designed to be highly performant and easy to deploy, python-oidc centralizes your OIDC logic, allowing your applications to focus solely on their business capabilities.
- FastAPI & Asyncio Native: Built on top of FastAPI and Uvicorn for high performance and concurrent request handling.
- OIDC Standard Compliance: Implements the core OpenID Connect authorization code flow.
- Configurable Endpoints: Customize the special "new token" endpoint path.
- Stateless by Design (for external cache): Currently uses an in-memory session store, making the core application logic less reliant on a separate cache database for its primary function.
- Session Management: Handles secure session creation and retrieval, including setting
HttpOnlycookies. - JOSE Operations: Leverages the
webcryptlibrary for robust JSON Web Signature (JWS) and JSON Web Encryption (JWE) operations (for future JWE session support).
- Python 3.9+
pipfor package management- Docker & Docker Compose (for Traefik integration example)
- Clone the repository:
git clone https://github.com/your-username/python-oidc.git # Replace with your repo URL cd python-oidc
- Install dependencies:
(Ensure
pip install -r requirements.txt
requirements.txtcontainsfastapi,uvicorn,pyyaml,pydantic,webcrypt, etc.)
python-oidc is configured via a clients.yaml file, which specifies details for your OIDC Identity Provider. For this initial version, we focus on configuring a single OIDC client.
1. Create clients.yaml:
Create a file named clients.yaml in the root directory of your project.
Example clients.yaml:
clients:
my_app_oidc: # This key ('my_app_oidc') becomes the unique identifier for your OIDC client
issuer: https://your-idp.com/realms/your-realm # e.g., Keycloak issuer URL, or https://accounts.google.com
client_id: your_oidc_client_id_from_idp
client_secret: "your_oidc_client_secret_from_idp"
redirect_uri: https://auth.your-app.com/oidc/my_app_oidc/callback # <--- IMPORTANT: This MUST match your externally accessible URL and be registered with your IdP!
scopes: "openid profile email offline_access" # Basic OIDC scopes
extra_claim_headers: # (Optional) Map OIDC claims to HTTP headers for your downstream apps
email: X-User-Email
preferred_username: X-User-Preferred-Username
# Add more mappings as neededKey Configuration Notes:
my_app_oidc: This key is your chosen unique identifier for this OIDC client. It will be part of the URL path (/oidc/my_app_oidc/...).issuer: The base URL of your OIDC Identity Provider.client_id&client_secret: Credentials obtained when you register your application with your IdP.redirect_uri: This is crucial for security and functionality. It must be the exact, full URL thatpython-oidcwill present to your IdP as its callback endpoint, and it MUST be pre-registered and allowed in your IdP's client configuration. In the Traefik example below, this will correspond to the publicly accessible domain Traefik exposes.scopes: The requested OpenID Connect scopes (e.g.,openid,profile,email).extra_claim_headers: (Optional) A mapping from OIDC claim names (e.g.,email) to HTTP header names (e.g.,X-User-Email). Claims decoded from the ID token will be forwarded as these headers to your downstream applications.
2. Environment Variables:
AUTH_DOMAIN: This environment variable (e.g.,https://auth.your-app.com) should be set to the base domain where yourpython-oidcinstance will be externally accessible. This is important for constructing internal URLs and ensures theredirect_uri(defined inclients.yaml) is consistent with your deployment.AUTH_NEW_TOKEN_PATH: (Optional) Defines the path segment for the "new token" endpoint. Default:new-token.
Using Docker Compose with Traefik is an excellent way to deploy and expose python-oidc securely.
-
Create a
docker-compose.ymlfile:version: '3.8' services: traefik: image: traefik:v2.10 # Use a stable Traefik v2 version command: - --api.insecure=true # Don't use in production, use a secure API with dashboard - --providers.docker=true - --providers.docker.exposedbydefault=false - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --certificatesresolvers.myresolver.acme.tlschallenge=true # For Let's Encrypt - --certificatesresolvers.myresolver.acme.email=your.email@example.com - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json ports: - "80:80" - "443:443" - "8080:8080" # Traefik Dashboard (insecure, remove for production or secure it) volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./letsencrypt:/letsencrypt # For storing Let's Encrypt certificates networks: - oidc_network python-oidc: build: . # Builds from your local Dockerfile container_name: python-oidc env_file: - .env # Load AUTH_DOMAIN and other environment variables from a .env file volumes: - ./clients.yaml:/app/clients.yaml:ro # Mount your clients.yaml into the container networks: - oidc_network labels: # Enable Traefik for this service - "traefik.enable=true" # Define HTTP entrypoint - "traefik.http.routers.oidc-http.entrypoints=web" - "traefik.http.routers.oidc-http.rule=Host(`auth.your-app.com`)" # <-- Replace with your domain - "traefik.http.routers.oidc-http.middlewares=oidc-redirect@docker" # Middleware for redirecting HTTP to HTTPS # Define HTTPS entrypoint - "traefik.http.routers.oidc-https.entrypoints=websecure" - "traefik.http.routers.oidc-https.rule=Host(`auth.your-app.com`)" # <-- Replace with your domain - "traefik.http.routers.oidc-https.tls=true" - "traefik.http.routers.oidc-https.tls.certresolver=myresolver" # Use Let's Encrypt resolver - "traefik.http.routers.oidc-https.service=python-oidc-service" # Define the service for Traefik to route to - "traefik.http.services.python-oidc-service.loadbalancer.server.port=8000" # FastAPI's default port # Middleware to redirect HTTP to HTTPS (optional but recommended) - "traefik.http.middlewares.oidc-redirect.redirectscheme.scheme=https" - "traefik.http.middlewares.oidc-redirect.redirectscheme.permanent=true" networks: oidc_network: external: false # Or true if you manage it outside compose
-
Create a
.envfile: In the same directory asdocker-compose.yml, create a.envfile for your environment variables:AUTH_DOMAIN=https://auth.your-app.com # <-- IMPORTANT: This MUST match the Host() rule in docker-compose.yml AUTH_NEW_TOKEN_PATH=new-token # Optional, default is 'new-token' -
Build and Run:
docker compose up --build -d
This will build your
python-oidcDocker image, start Traefik, and route requests fromauth.your-app.comto yourpython-oidccontainer.
Once python-oidc is running behind Traefik (e.g., at https://auth.your-app.com), you can use its authentication flow:
Assuming your idp_identifier (from clients.yaml) is my_app_oidc:
-
Initiate Login: Direct your users (or your application) to:
https://auth.your-app.com/oidc/my_app_oidc/loginpython-oidcwill redirect the user to your configured IdP's login page.
-
Callback Handling: After successful authentication at the IdP, the IdP will redirect back to:
https://auth.your-app.com/oidc/my_app_oidc/callbackpython-oidcwill process the authorization code, exchange it for tokens, validate them, and establish an in-memory session.
-
New Token Endpoint (Session Refresh): For applications needing to refresh an expired access token or obtain a new session cookie:
https://auth.your-app.com/oidc/my_app_oidc/new-token/{session_key}(Note:new-tokenis the default path segment, configurable viaAUTH_NEW_TOKEN_PATH). This endpoint will typically check the existing session and handle token refreshing and redirecting the user.
python-oidc currently uses an in-memory session store. This means:
- Single Instance: The application, in its current state, is designed to run as a single instance. If the process restarts (e.g., due to deployment, crash, or manual restart), all active sessions stored in RAM will be lost, and users will need to re-authenticate.
- High Performance: For a single instance, this provides extremely fast session access as no external database round-trips are needed.
- Future Extension: The session store is implemented with an abstract interface, laying the groundwork for easy integration with external cache databases (like Redis) in the future. This will enable horizontal scalability and session persistence across restarts.
Contributions are welcome! Please feel free to open an issue or submit a pull request.
This project is licensed under the Apache License 2.0. See the LICENSE file for details.