Skip to main content

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.

Metadata

Release files for py-oidc-auth 2606.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for py-oidc-auth 2606.0.0
File Size Uploaded Provenance
py_oidc_auth-2606.0.0.tar.gz 2.8 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for py-oidc-auth 2606.0.0
File Interpreter ABI Platform
py_oidc_auth-2606.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 2.8 MB

Release files / py_oidc_auth-2606.0.0.tar.gz

Download URL py_oidc_auth-2606.0.0.tar.gz
Size 2.8 MB
Tags Source
SHA-256 checksum
How to use checksums
130ee505b6314d230d38ff0d9ec21a17c610269bc6342f2010de0a4630deb130
BLAKE2b-256 checksum
How to use checksums
4f5661303420248ce0fa298302faea1dda07d81138de9829911bf7b50dab27bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12

Release files / py_oidc_auth-2606.0.0-py3-none-any.whl

Download URL py_oidc_auth-2606.0.0-py3-none-any.whl
Size 59.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
953407d73db95a8cf4f0f5ec1fcf07d3030f1dafd0defd20f4e2a7e2003ee8e4
BLAKE2b-256 checksum
How to use checksums
1c1dd606c10c78c06927575621dca5d44e265dd6efa473804b508bec1bc45495
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12

Security for py-oidc-auth 2606.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page