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
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.
|
FastAPI |
Flask |
Quart |
|
Tornado |
Litestar |
Django |
- 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 🏷️
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-litestart
conda install -c conda-forge py-oidc-auth-djangoImport name is py_oidc_auth:
from py_oidc_auth import OIDCAuthOIDCAuth is the framework independent client. It loads provider metadata from the
OpenID Connect discovery document, performs provider calls, and validates tokens.
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
Adapters can expose these paths (customizable and individually disableable):
GET /auth/v2/loginGET /auth/v2/callbackPOST /auth/v2/tokenPOST /auth/v2/deviceGET /auth/v2/logoutGET /auth/v2/userinfo
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",
)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",
)
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}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",
)
app.register_blueprint(auth.create_auth_blueprint(prefix="/api"))
@app.get("/protected")
@auth.required()
def protected(token: IDToken) -> Response:
return jsonify({"sub": token.sub})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",
)
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})Decorator style:
from django.http import HttpRequest, JsonResponse
from django.urls import 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",
)
@auth.required()
async def protected_view(request: HttpRequest, token: IDToken) -> JsonResponse:
return JsonResponse({"sub": token.sub})
urlpatterns = [
*auth.get_urlpatterns(prefix="api"),
path("protected/", protected_view),
]Routes only:
urlpatterns = [
*auth.get_urlpatterns(prefix="api"),
]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",
)
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_handlers(prefix="/api") + [
(r"/protected", ProtectedHandler),
]
)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",
)
@get("/protected")
@auth.required()
async def protected(token: IDToken) -> Dict[str, str]:
return {"sub": token.sub}
app = Litestar(
route_handlers=[
protected,
*auth.get_route_handlers(prefix="/api"),
]
)All adapters support:
scopes="a b c"to require scopes on a protected endpointclaims={...}to enforce simple claim constraints
FastApi Example:
@auth.required(scopes="admin", claims={"groups": ["admins"]})
def admin(token: IDToken) -> Dict[str, str]:
return {"sub": token.sub}- py-oidc-auth-client — typed Python client for authenticating against services that expose py-oidc-auth-compatible routes.
See the CONTRIBUTING.md document to get involved.
