Skip to content

plataux/python-oidc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

python-oidc: FastAPI-based OIDC Authentication Middleware

🌟 Overview

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.

✨ Features

  • 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 HttpOnly cookies.
  • JOSE Operations: Leverages the webcrypt library for robust JSON Web Signature (JWS) and JSON Web Encryption (JWE) operations (for future JWE session support).

🚀 Getting Started

Prerequisites

  • Python 3.9+
  • pip for package management
  • Docker & Docker Compose (for Traefik integration example)

Installation

  1. Clone the repository:
    git clone https://github.com/your-username/python-oidc.git # Replace with your repo URL
    cd python-oidc
  2. Install dependencies:
    pip install -r requirements.txt
    (Ensure requirements.txt contains fastapi, uvicorn, pyyaml, pydantic, webcrypt, etc.)

Configuration (Single OIDC Client)

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 needed

Key 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 that python-oidc will 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 your python-oidc instance will be externally accessible. This is important for constructing internal URLs and ensures the redirect_uri (defined in clients.yaml) is consistent with your deployment.
  • AUTH_NEW_TOKEN_PATH: (Optional) Defines the path segment for the "new token" endpoint. Default: new-token.

Running with Docker Compose and Traefik (Recommended)

Using Docker Compose with Traefik is an excellent way to deploy and expose python-oidc securely.

  1. Create a docker-compose.yml file:

    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
  2. Create a .env file: In the same directory as docker-compose.yml, create a .env file 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'
    
  3. Build and Run:

    docker compose up --build -d

    This will build your python-oidc Docker image, start Traefik, and route requests from auth.your-app.com to your python-oidc container.

🚀 Usage

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:

  1. Initiate Login: Direct your users (or your application) to:

    • https://auth.your-app.com/oidc/my_app_oidc/login python-oidc will redirect the user to your configured IdP's login page.
  2. Callback Handling: After successful authentication at the IdP, the IdP will redirect back to:

    • https://auth.your-app.com/oidc/my_app_oidc/callback python-oidc will process the authorization code, exchange it for tokens, validate them, and establish an in-memory session.
  3. 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-token is the default path segment, configurable via AUTH_NEW_TOKEN_PATH). This endpoint will typically check the existing session and handle token refreshing and redirecting the user.

🧪 Session Management Details

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.

🤝 Contributing

Contributions are welcome! Please feel free to open an issue or submit a pull request.

📄 License

This project is licensed under the Apache License 2.0. See the LICENSE file for details.

About

Cloud Native OIDC Middleware Service in Python for Traefik

Resources

License

Stars

Watchers

Forks

Languages