Skip to main content

OAuth 2.1 authorization server for Belgie

Project description

Belgie OAuth Server

[!WARNING] This package keeps client, token, and authorization state in memory by default through SimpleOAuthProvider. That is fine for development and tests, but production deployments should use a persistent provider implementation.

Belgie OAuth Server is the OAuth 2.1 authorization server package for Belgie apps. It gives you the server-side OAuth plumbing, metadata endpoints, PKCE handling, dynamic client registration, and prompt-aware login flows without leaving the Python stack.

It is designed to pair with belgie-core and FastAPI. The package exposes a small settings object, a plugin, a client helper for custom auth pages, and metadata builders for OAuth, OpenID Connect, and protected resource discovery.

Installation

uv add belgie-oauth-server

What It Covers

  • OAuth 2.1 authorization, token, revoke, introspect, and userinfo routes.
  • OpenID Connect metadata and id_token support.
  • OAuth protected resource metadata when you configure resources=[OAuthResource(...)].
  • Dynamic client registration, including the anonymous registration escape hatch when you explicitly enable it.
  • Custom login and signup pages via login_url and signup_url.

Important Notes

  • Resource matching is strict. If a client sends resource and no OAuth resource is configured, the server returns invalid_target.
  • SimpleOAuthProvider keeps secrets in memory too. Use a persistent provider before shipping.
  • allow_unauthenticated_client_registration=True is intentionally permissive. Treat it as a compatibility or development setting unless you have separate controls around registration.

Examples

  • Custom pages: prompt-aware login and signup routes with OAuthServerClient.
  • MCP auth: OAuth server configuration paired with an MCP resource server.

Quick Start

Here is the smallest practical setup for a Belgie OAuth server with custom login pages:

Project Structure:

my-app/
├── server.py
└── views/
    └── ...

server.py:

from collections.abc import AsyncGenerator
from typing import Annotated

from fastapi import Depends, FastAPI, Request
from fastapi.responses import RedirectResponse
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

from belgie import Belgie, BelgieClient, BelgieSettings
from belgie.alchemy import BelgieAdapter
from belgie.oauth.server import OAuthServer, OAuthServerClient
from yourapp.models import Account, OAuthState, Session, Individual

app = FastAPI()

settings = BelgieSettings(
    secret="change-me",
    base_url="http://localhost:8000",
)

engine = create_async_engine("sqlite+aiosqlite:///./app.db")
session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)


async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with session_maker() as session:
        yield session


adapter = BelgieAdapter(
    user=Individual,
    account=Account,
    session=Session,
    oauth_state=OAuthState,
)

belgie = Belgie(settings=settings, adapter=adapter, database=get_db)

oauth_plugin = belgie.add_plugin(
    OAuthServer(
        base_url=settings.base_url,
        client_id="demo-client",
        client_secret="demo-secret",
        redirect_uris=["http://localhost:3030/callback"],
        login_url="/login",
        signup_url="/signup",
    ),
)

app.include_router(belgie.router)


@app.get("/login")
async def login(
    request: Request,
    oauth: Annotated[OAuthServerClient, Depends(oauth_plugin)],
) -> RedirectResponse:
    context = await oauth.try_resolve_login_context(request)
    if context is None:
        return RedirectResponse(url="/login/google", status_code=302)
    if context.intent == "create":
        return RedirectResponse(url=f"/signup?state={context.state}", status_code=302)
    return RedirectResponse(url=f"/login/google?state={context.state}", status_code=302)


@app.get("/signup")
async def signup(
    request: Request,
    oauth: Annotated[OAuthServerClient, Depends(oauth_plugin)],
    client: Annotated[BelgieClient, Depends(belgie)],
) -> RedirectResponse:
    context = await oauth.resolve_login_context(request)
    response = RedirectResponse(url=context.return_to, status_code=302)
    _user, session = await client.sign_up("dev@example.com", request=request)
    return client.create_session_cookie(session, response)

Run the app with:

uv run uvicorn server:app --reload

Configuration

  • prefix controls where the OAuth server routes are mounted. The default is /oauth.
  • base_url is used to derive issuer and metadata URLs.
  • redirect_uris is required and must contain at least one callback URL.
  • resources=[OAuthResource(prefix=..., scopes=...)] enables protected resource metadata.
  • enable_end_session turns on RP-initiated logout support.
  • allow_dynamic_client_registration enables POST /auth/oauth/register.
  • allow_unauthenticated_client_registration lets anonymous callers register clients without authentication.

Login Flow

  • prompt=create prefers signup_url when it is configured.
  • Otherwise login_url is used.
  • OAuthServerClient.try_resolve_login_context(request) returns None when no OAuth state is present, which makes it easy to support both direct visits and redirect-driven entry points.

Migration Note

  • route_prefix has been removed. Use prefix instead.
  • resource_server_url has been removed. Use resources=[OAuthResource(...)] instead.
  • resource_scopes has been removed. Put scopes on OAuthResource(scopes=[...]) instead.

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

belgie_oauth_server-0.14.0.tar.gz (18.5 kB view details)

Uploaded Source

Built Distribution

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

belgie_oauth_server-0.14.0-py3-none-any.whl (22.8 kB view details)

Uploaded Python 3

File details

Details for the file belgie_oauth_server-0.14.0.tar.gz.

File metadata

  • Download URL: belgie_oauth_server-0.14.0.tar.gz
  • Upload date:
  • Size: 18.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for belgie_oauth_server-0.14.0.tar.gz
Algorithm Hash digest
SHA256 6cb3757542fec9760a7e7b4511b8c3f0764aa565dbc01d14aa21f8174710ca9d
MD5 c2cc019b8fc344d7c363e4e04d31dade
BLAKE2b-256 89a7de764e3e2f3358616746121bd81167b55d8fe9dece9d47a703a9fdf93932

See more details on using hashes here.

File details

Details for the file belgie_oauth_server-0.14.0-py3-none-any.whl.

File metadata

  • Download URL: belgie_oauth_server-0.14.0-py3-none-any.whl
  • Upload date:
  • Size: 22.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for belgie_oauth_server-0.14.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2ea76dfd3c897e61cf6930a9b6640cbc7b432907d9def53c98a23f169306d7ec
MD5 2da2801b7c5389c574a249d1cc54fe7e
BLAKE2b-256 7c06474ea80c5206a10a74d704768d1ebbe380fbe4c2b6fa5993e2aea6beb48c

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