Modern authentication for FastAPI
Project description
Belgie
Self-hosted, type-safe authentication for FastAPI that makes Google OAuth and secure session cookies work with almost zero glue code. Keep your data, skip per-user SaaS bills, and still get a polished developer experience.
Who this is for
- FastAPI teams that want Google sign-in and protected routes today, not after weeks of wiring.
- Product engineers who prefer first-class type hints and adapter-driven design over magic.
- Startups that would rather own their user data and avoid per-MAU pricing from hosted identity vendors.
What it solves
- End-to-end Google OAuth 2.0 flow with CSRF-safe state storage.
- Sliding-window, signed session cookies (no JWT juggling required).
- Drop-in FastAPI dependencies for
auth.user,auth.session, and scoped access. - A thin SQLAlchemy adapter that works with your existing models.
- Hooks so you can plug in logging, analytics, or audit trails without forking.
How it compares
- fastapi-users: feature-rich but now in maintenance mode and optimized for password-plus-OAuth flows. Belgie focuses on OAuth + session UX, keeps the surface area small, and ships type-driven adapters out of the box.
- Hosted identity (Auth0, Clerk, Supabase Auth): great UIs and more providers, but billed per Monthly Active User and hosted off your stack. Belgie is MIT-licensed, runs in your app, and never charges per user.
Features at a glance
- Google OAuth provider with ready-made router (
/auth/signin/google,/auth/callback/google,/auth/signout). - Session manager with sliding expiry and secure cookie defaults (HttpOnly, SameSite, Secure).
- Scope-aware dependency for route protection (
Security(auth.user, scopes=[...])). - Modern Python (3.12+), full typing, and protocol-based models.
- Event hooks and utility helpers for custom workflows.
Installation
pip install belgie
# or with uv
uv add belgie
For SQLAlchemy adapter support:
pip install belgie[alchemy]
# or with uv
uv add belgie[alchemy]
Optional extras: belgie[mcp], belgie[oauth], or belgie[all].
Quick start
1) Define models
from datetime import UTC, datetime
from uuid import UUID, uuid4
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
name: Mapped[str | None] = mapped_column(String(255), nullable=True)
image: Mapped[str | None] = mapped_column(String(500), nullable=True)
email_verified: Mapped[bool] = mapped_column(default=False)
created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC))
updated_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC))
class Account(Base):
__tablename__ = "accounts"
id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
provider: Mapped[str] = mapped_column(String(50))
provider_account_id: Mapped[str] = mapped_column(String(255))
access_token: Mapped[str | None] = mapped_column(String(1000), nullable=True)
refresh_token: Mapped[str | None] = mapped_column(String(1000), nullable=True)
expires_at: Mapped[datetime | None] = mapped_column(nullable=True)
scope: Mapped[str | None] = mapped_column(String(500), nullable=True)
created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC))
class Session(Base):
__tablename__ = "sessions"
id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
expires_at: Mapped[datetime] = mapped_column(index=True)
created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC))
class OAuthState(Base):
__tablename__ = "oauth_states"
id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
state: Mapped[str] = mapped_column(String(255), unique=True, index=True)
expires_at: Mapped[datetime] = mapped_column(index=True)
created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC))
2) Configure Belgie
from belgie.auth import Auth, AuthSettings, GoogleProviderSettings
from belgie_alchemy import AlchemyAdapter
settings = AuthSettings(
secret="your-secret-key",
base_url="http://localhost:8000",
)
adapter = AlchemyAdapter(
user=User,
account=Account,
session=Session,
oauth_state=OAuthState,
)
auth = Auth(
settings=settings,
adapter=adapter,
providers={
"google": GoogleProviderSettings(
client_id="your-google-client-id",
client_secret="your-google-client-secret",
redirect_uri="http://localhost:8000/auth/provider/google/callback",
scopes=["openid", "email", "profile"],
),
},
)
3) Add routes to FastAPI
from fastapi import Depends, FastAPI, Security
app = FastAPI()
app.include_router(auth.router)
@app.get("/")
async def home():
return {"message": "Welcome! Visit /auth/provider/google/signin to sign in"}
@app.get("/protected")
async def protected(user: User = Depends(auth.user)):
return {"email": user.email}
@app.get("/profile")
async def profile(user: User = Security(auth.user, scopes=["profile"])):
return {"name": user.name, "email": user.email}
Run it:
uvicorn main:app --reload
Visit http://localhost:8000/auth/signin/google to sign in.
Configuration shortcuts
- Environment variables:
BELGIE_SECRET,BELGIE_BASE_URL,BELGIE_GOOGLE_CLIENT_ID,BELGIE_GOOGLE_CLIENT_SECRET,BELGIE_GOOGLE_REDIRECT_URI(loaded automatically byAuthSettings()). - Session tuning:
SessionSettings(cookie_name, max_age, update_age)controls lifetime and sliding refresh. - Cookie hardening:
CookieSettings(http_only, secure, same_site)for production-ready defaults.
Router endpoints
GET /auth/signin/google– start OAuth flowGET /auth/callback/google– handle Google callbackPOST /auth/signout– clear session cookie and invalidate server session
Limitations today
- Google is the only built-in provider; more providers and email/password are on the roadmap.
- You manage your own database migrations and deployment (by design—no third-party control plane).
Why teams pick Belgie
- Keep control of data and infra while getting a batteries-included OAuth flow.
- Minimal surface area: a single
Authinstance exposes router + dependencies. - Modern typing and clear protocols reduce integration mistakes and make refactors safer.
- MIT license, zero per-user costs.
Documentation and examples
- docs/quickstart.md for full walkthrough
- examples/auth for a runnable app
Contributing
MIT licensed. Issues and PRs welcome.
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 belgie-0.3.0.tar.gz.
File metadata
- Download URL: belgie-0.3.0.tar.gz
- Upload date:
- Size: 5.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
871437b4afc947ead61240b1875367374bbfc992e9742dbacfab30f936fe5c58
|
|
| MD5 |
e19634de25f7268b719890508d1b82fb
|
|
| BLAKE2b-256 |
156a4518a6b0b4fdba071ea3bab7450f1c5c0a80cbb5d34e0c4251e2e9c11e5d
|
File details
Details for the file belgie-0.3.0-py3-none-any.whl.
File metadata
- Download URL: belgie-0.3.0-py3-none-any.whl
- Upload date:
- Size: 6.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09d613199cd220aab1a21ab605165cbf7f334cba2b0c1eac65adfb1aad033c88
|
|
| MD5 |
57f82b41bd9391a3a4f4ff85070ae5ba
|
|
| BLAKE2b-256 |
0eccae4efcd1fb72207016fc3d488bfe1b388a4ebde43ed18d59228c68709b72
|