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.1.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.1-py3-none-any.whl (58.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: py_oidc_auth-2604.2.1.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.1.tar.gz
Algorithm Hash digest
SHA256 0b0bffc70b843cd6264fd266cfae3642c71349bc0520a8ddfa52876f63e29a1d
MD5 a66d596b8f8f035a8151edf7d73d3ad3
BLAKE2b-256 7257d5845f9cee8ecc7d631955873e8a2240ab1def60bd9b12d0058028cb24ec

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for py_oidc_auth-2604.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4e2f88c7318636ad18c1782321d7972584a61cd99c27e6f3e8d792748b51ba49
MD5 c46af27d278df24205e70273ed1811d1
BLAKE2b-256 46976e728682cf31b234fad4fecf98684164be417cddcbb6a521e331b29357a3

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