Skip to main content

Pluggable OIDC/OAuth 2.1 authentication routes and token validation.

Project description

py-oidc-auth logo

A small, typed OpenID Connect helper for authentication and authorization.

License codecov docs PyPI Python Versions

It provides

  • a framework independent async core: OIDCAuth
  • framework adapters that expose common auth endpoints
  • simple required() and optional() helpers to protect routes
  • token minting/brokering and token federation

What it does

py-oidc-auth adds OpenID Connect authentication to your Python web application. You create one auth instance at app startup, get a pre-built router (or blueprint / URL patterns), optionally add your own custom routes to it, and include it in your app. Protected routes use required() and optional() helpers.

Supported frameworks

FastAPI
FastAPI
Flask
Flask
Quart
Quart
Tornado
Tornado
Litestar
Litestar
Django
Django

Features

  • Authorization code flow with PKCE (login and callback)
  • Refresh token flow
  • Device authorization flow
  • Userinfo lookup
  • Provider initiated logout (end session) when supported
  • Bearer token validation using provider JWKS, issuer, and audience
  • Optional scope checks and simple claim constraints
  • Full type annotation 🏷️

Install

Pick your framework for installation with pip:

python -m pip install py-oidc-auth[fastapi]
python -m pip install py-oidc-auth[flask]
python -m pip install py-oidc-auth[quart]
python -m pip install py-oidc-auth[tornado]
python -m pip install py-oidc-auth[litestar]
python -m pip install py-oidc-auth[django]

Or use conda/mamba/micromamba:

conda install -c conda-forge py-oidc-auth-fastapi
conda install -c conda-forge py-oidc-auth-flask
conda install -c conda-forge py-oidc-auth-quart
conda install -c conda-forge py-oidc-auth-tornado
conda install -c conda-forge py-oidc-auth-litestar
conda install -c conda-forge py-oidc-auth-django

Import name is py_oidc_auth:

from py_oidc_auth import OIDCAuth

Concepts

Core

OIDCAuth is the framework independent client. It loads provider metadata from the OpenID Connect discovery document, performs provider calls, and validates tokens.

Adapters

Each adapter subclasses OIDCAuth and adds:

  • helpers to register the standard endpoints (router, blueprint, urlpatterns, etc.)
  • required() and optional() helpers to validate bearer tokens on protected routes

Default endpoints

Adapters can expose these paths (customizable and individually disabled):

  • GET /auth/v2/login
  • GET /auth/v2/callback
  • POST /auth/v2/token
  • POST /auth/v2/device
  • GET /auth/v2/logout
  • GET /auth/v2/userinfo
  • GET /auth/v2/.well-known/jwks.json

Quick start

Create one auth instance at app startup:

auth = ...(
    client_id="my client",
    client_secret="secret",
    discovery_url="https://idp.example.org/realms/demo/.well-known/openid-configuration",
    scopes="myscope profile email",
    audience="my-aud",
    broker_mode=True,
    broker_store_url="postgresql+asyncpg://user:pw@db/myapp",
    broker_audience="myapp-api",
    trusted_issuers=["https://other-instance.example.org"],
)

FastAPI

from typing import Dict, Optional

from fastapi import FastAPI
from py_oidc_auth import FastApiOIDCAuth, IDToken

app = FastAPI()

auth = FastApiOIDCAuth(
    client_id="my client",
    client_secret="secret",
    discovery_url="https://idp.example.org/realms/demo/.well-known/openid-configuration",
    scopes="myscope profile email",
    audience="my-aud",
    broker_mode=True,
    broker_store_url="postgresql+asyncpg://user:pw@db/myapp",
    broker_audience="myapp-api",
    trusted_issuers=["https://other-instance.example.org"],
)

app.include_router(auth.create_auth_router(prefix="/api"))

@app.get("/me")
async def me(token: IDToken = auth.required()) -> Dict[str, str]:
    return {"sub": token.sub}

@app.get("/feed")
async def feed(token: Optional[IDToken] = auth.optional() -> Dict[str, str]:
    if token is None:
       message = "Welcome guest"
    else:
       message = "Welcome back, {token.given_name}"
    return {"message": message}

Flask

from flask import Flask, Response, jsonify
from py_oidc_auth import FlaskOIDCAuth

app = Flask(__name__)

auth = FlaskOIDCAuth(
    client_id="my client",
    client_secret="secret",
    discovery_url="https://idp.example.org/realms/demo/.well-known/openid-configuration",
    scopes="myscope profile email",
    audience="my-aud",
    broker_mode=True,
    broker_store_url="postgresql+asyncpg://user:pw@db/myapp",
    broker_audience="myapp-api",
    trusted_issuers=["https://other-instance.example.org"],
)

app.register_blueprint(auth.create_auth_blueprint(prefix="/api"))

@app.get("/protected")
@auth.required()
def protected(token: IDToken) -> Response:
    return jsonify({"sub": token.sub})

Quart

from quart import Quart, Response, jsonify
from py_oidc_auth import QuartOIDCAuth, IDToken

app = Quart(__name__)

auth = QuartOIDCAuth(
    client_id="my client",
    client_secret="secret",
    discovery_url="https://idp.example.org/realms/demo/.well-known/openid-configuration",
    scopes="myscope profile email",
    audience="my-aud",
    broker_mode=True,
    broker_store_url="postgresql+asyncpg://user:pw@db/myapp",
    broker_audience="myapp-api",
    trusted_issuers=["https://other-instance.example.org"],
)

app.register_blueprint(auth.create_auth_blueprint(prefix="/api"))

@app.get("/protected")
@auth.required()
async def protected(token: IDToken) -> Response:
    return jsonify({"sub": token.sub})

Django

Decorator style:

from django.http import HttpRequest, JsonResponse
from django.urls import include, path
from py_oidc_auth import DjangoOIDCAuth, IDToken

auth = DjangoOIDCAuth(
    client_id="my client",
    client_secret="secret",
    discovery_url="https://idp.example.org/realms/demo/.well-known/openid-configuration",
    scopes="myscope profile email",
    audience="my-aud",
    broker_mode=True,
    broker_store_url="postgresql+asyncpg://user:pw@db/myapp",
    broker_audience="myapp-api",
    trusted_issuers=["https://other-instance.example.org"],
)

@auth.required()
async def protected_view(request: HttpRequest, token: IDToken) -> JsonResponse:
    return JsonResponse({"sub": token.sub})

urlpatterns = [
    path("api/", include(auth.get_urlpatterns())),
    path("protected/", protected_view),
]

Routes only:

urlpatterns = [
    *auth.get_urlpatterns(prefix="api"),
    path("api/", include(...))
]

Tornado

import json
import tornado.web
from py_oidc_auth import TornadoOIDCAuth, IDToken

auth = TornadoOIDCAuth(
    client_id="my client",
    client_secret="secret",
    discovery_url="https://idp.example.org/realms/demo/.well-known/openid-configuration",
    scopes="myscope profile email",
    audience="my-aud",
    broker_mode=True,
    broker_store_url="postgresql+asyncpg://user:pw@db/myapp",
    broker_audience="myapp-api",
    trusted_issuers=["https://other-instance.example.org"],
)

class ProtectedHandler(tornado.web.RequestHandler):
    @auth.required()
    async def get(self, token: IDToken) -> None:
        self.write(json.dumps({"sub": token.sub}))

def make_app():
    return tornado.web.Application(
        auth.get_auth_routes(prefix="/api") + [
            (r"/protected", ProtectedHandler),
        ]
    )

Litestar

from typing import Dict
from litestar import Litestar, get
from py_oidc_auth import LitestarOIDCAuth

auth = LitestarOIDCAuth(
    client_id="my client",
    client_secret="secret",
    discovery_url="https://idp.example.org/realms/demo/.well-known/openid-configuration",
    scopes="myscope profile email",
    audience="my-aud",
    broker_mode=True,
    broker_store_url="postgresql+asyncpg://user:pw@db/myapp",
    broker_audience="myapp-api",
    trusted_issuers=["https://other-instance.example.org"],
)

@get("/protected")
@auth.required()
async def protected(token: IDToken) -> Dict[str, str]:
    return {"sub": token.sub}

app = Litestar(
    route_handlers=[
        protected,
        *auth.create_auth_route(prefix="/api"),
    ]
)

Scopes audience and claim constraints

All adapters support:

  • scopes="a b c" to require scopes on a protected endpoint
  • claims={...} to enforce simple claim constraints
  • audience=my-aud to enforce intended audience check

FastApi Example:

@auth.required(scopes="admin", claims={"groups": ["admins"]})
def admin(token: IDToken) -> Dict[str, str]:
    return {"sub": token.sub}

Token minting and federation

The broker_mode=True option allows for the creation of minting of application specific tokens rather than passing tokens from the Identity Provider.

Token minting also allows for token federation where multiple applications can be configured to trust each others tokens.

Related

  • py-oidc-auth-client — typed Python client for authenticating against services that expose py-oidc-auth-compatible routes.

Contributing

See the CONTRIBUTING.md document to get involved.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_oidc_auth-2604.2.2.tar.gz (2.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_oidc_auth-2604.2.2-py3-none-any.whl (58.5 kB view details)

Uploaded Python 3

File details

Details for the file py_oidc_auth-2604.2.2.tar.gz.

File metadata

  • Download URL: py_oidc_auth-2604.2.2.tar.gz
  • Upload date:
  • Size: 2.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for py_oidc_auth-2604.2.2.tar.gz
Algorithm Hash digest
SHA256 bbf6ab433420932d62197e54071163a98e83d17c8903b87366c99e3bb17d848b
MD5 9503ad7e07a6ede23dc4a64bb3ae3c80
BLAKE2b-256 d4682add7a786edbcfa02a656478fc48da060825e0b5e663245a581ef4e5f3bc

See more details on using hashes here.

File details

Details for the file py_oidc_auth-2604.2.2-py3-none-any.whl.

File metadata

File hashes

Hashes for py_oidc_auth-2604.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 defb721b4dd106edc39787ebcfe136a4c7235eb5b8012fa5a9ca659d6eb978c5
MD5 deab803aaab225af9fdb28b8dc4c4882
BLAKE2b-256 41150328598ba01dea805858eb2003abd172c4ab66dc5ffb44b71ecfd9895dbf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page