Pluggable OIDC/OAuth 2.1 authentication routes and token validation.
Project description
A small, typed OpenID Connect helper for authentication and authorization.
It provides
- a framework independent async core:
OIDCAuth - framework adapters that expose common auth endpoints
- simple
required()andoptional()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 |
Flask |
Quart |
|
Tornado |
Litestar |
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()andoptional()helpers to validate bearer tokens on protected routes
Default endpoints
Adapters can expose these paths (customizable and individually disabled):
GET /auth/v2/loginGET /auth/v2/callbackPOST /auth/v2/tokenPOST /auth/v2/deviceGET /auth/v2/logoutGET /auth/v2/userinfoGET /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 endpointclaims={...}to enforce simple claim constraintsaudience=my-audto 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0b0bffc70b843cd6264fd266cfae3642c71349bc0520a8ddfa52876f63e29a1d
|
|
| MD5 |
a66d596b8f8f035a8151edf7d73d3ad3
|
|
| BLAKE2b-256 |
7257d5845f9cee8ecc7d631955873e8a2240ab1def60bd9b12d0058028cb24ec
|
File details
Details for the file py_oidc_auth-2604.2.1-py3-none-any.whl.
File metadata
- Download URL: py_oidc_auth-2604.2.1-py3-none-any.whl
- Upload date:
- Size: 58.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e2f88c7318636ad18c1782321d7972584a61cd99c27e6f3e8d792748b51ba49
|
|
| MD5 |
c46af27d278df24205e70273ed1811d1
|
|
| BLAKE2b-256 |
46976e728682cf31b234fad4fecf98684164be417cddcbb6a521e331b29357a3
|