OAuth 2.1 + BFF (Backend-for-Frontend) authentication library for Python
Project description
BFFAuth
OAuth 2.1 + BFF (Backend-for-Frontend) Authentication Library for Python.
Table of Contents
- What is the BFF Pattern?
- Why Use BFF Instead of Standard OAuth?
- Architecture
- How BFFAuth Works
- Installation
- Quick Start
- Supported Providers
- Security Features
- API Reference
- Competitive Analysis
- References
What is the BFF Pattern?
BFF (Backend-for-Frontend) is a security architecture pattern for OAuth in browser-based applications. It is the #1 recommended approach in the IETF draft-ietf-oauth-browser-based-apps specification (currently at version 18, on track to become an RFC).
The Problem with Traditional SPA OAuth
In traditional Single Page Applications (SPAs), OAuth tokens are stored in the browser:
Traditional SPA OAuth (INSECURE for browsers)
═══════════════════════════════════════════════
┌─────────────┐ ┌──────────────────┐
│ Browser │◄─────────────────────│ OAuth Provider │
│ (SPA) │ Returns tokens │ (Google, Azure) │
│ │ directly to browser │ │
└─────────────┘ └──────────────────┘
│
│ Tokens stored in:
│ • localStorage (persists, vulnerable to XSS)
│ • sessionStorage (cleared on tab close)
│ • JavaScript memory (lost on refresh)
│
▼
┌─────────────────────────────────────────────────┐
│ ⚠️ XSS VULNERABILITY │
│ Any JavaScript (including malicious scripts) │
│ can read tokens and send them to attackers │
└─────────────────────────────────────────────────┘
The BFF Solution
The BFF pattern keeps tokens server-side only, using secure cookies for session management:
BFF Pattern (IETF Recommended)
══════════════════════════════
┌─────────────┐ Cookie Only ┌───────────────────────────────────┐
│ Browser │◄────────────────────►│ BFF Layer │
│ (SPA) │ (Secure, HttpOnly, │ │
│ │ SameSite=Strict) │ ┌─────────────────────────────┐ │
│ │ │ │ Session Manager │ │
│ NO tokens │ │ │ • Cookie ↔ Session binding │ │
│ in browser │ │ │ • CSRF token validation │ │
│ │ │ └─────────────────────────────┘ │
└─────────────┘ │ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Token Vault │ │
│ │ • Encrypted at rest │ │
│ │ • Per-user, per-provider │ │
│ │ • Auto-refresh │ │
│ └─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ OAuth Client (Authlib) │ │
│ │ • PKCE │ │
│ │ • Token exchange │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ Resource Server Allowlist │
│ (Only approved APIs receive │
│ tokens - IETF 6.2.4) │
└───────────────────────────────────┘
Why Use BFF Instead of Standard OAuth?
Security Comparison
| Aspect | Traditional OAuth | BFF Pattern |
|---|---|---|
| Token Location | Browser (localStorage/memory) | Server-side only |
| XSS Vulnerability | Tokens can be stolen | Tokens never exposed |
| Session Management | Token-based | Secure cookie-based |
| CSRF Protection | State parameter only | SameSite + CSRF tokens |
| Token Refresh | Client-side (complex) | Server-side (transparent) |
| API Calls | Browser → API (with token) | Browser → BFF → API |
| Multi-Provider | Complex client-side | Simple server-side vault |
Key Benefits of BFF
-
XSS Protection: Even if an attacker injects malicious JavaScript, they cannot steal OAuth tokens because tokens never reach the browser.
-
Simplified Frontend: Your SPA doesn't need to manage tokens, refresh logic, or handle OAuth complexity.
-
Centralized Security: All security logic (token validation, refresh, allowlist) is in one place on the server.
-
Multi-Provider Support: Easily connect multiple OAuth providers (Google, Azure, GitHub) with tokens stored in a unified vault.
-
Confidential Client: BFF can use client secrets (unlike browser apps), providing stronger authentication with providers.
User Experience Comparison
| Aspect | Traditional OAuth | BFF Pattern | User Notices? |
|---|---|---|---|
| Click "Login" | Same | Same | No |
| Redirect to provider | Same | Same | No |
| Authenticate | Same | Same | No |
| Grant consent | Same | Same | No |
| Redirect back | Same | Same | No |
| Use features | Same | Same | No |
The user experience is identical - BFF is a transparent security enhancement.
Protocol Compatibility
BFF is fully compatible with OAuth 2.0/2.1. It doesn't change the OAuth protocol - it changes where the OAuth client runs and how tokens are handled after issuance.
| Provider | Compatible? | Notes |
|---|---|---|
| ✅ Yes | No changes needed | |
| Azure AD / Entra ID | ✅ Yes | No changes needed |
| GitHub | ✅ Yes | No changes needed |
| Okta | ✅ Yes | No changes needed |
| Auth0 | ✅ Yes | No changes needed |
| Any OAuth 2.0 provider | ✅ Yes | Standard protocol |
Architecture
BFFAuth Component Architecture
┌───────────────────────────────────────────────────────────────────────────┐
│ BFFAuth Library │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ BFFOAuthHandler │ │
│ │ • Orchestrates all components │ │
│ │ • Manages OAuth flows │ │
│ │ • Provides unified API │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────┼─────────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌─────────────────┐ ┌──────────────────┐ │
│ │SessionManager │ │ TokenVault │ │ Allowlist │ │
│ ├───────────────┤ ├─────────────────┤ │ Validator │ │
│ │• __Host- │ │• Fernet encrypt │ ├──────────────────┤ │
│ │ session │ │ (AES-128-CBC) │ │• URL pattern │ │
│ │ cookies │ │• Multi-provider │ │ matching │ │
│ │• CSRF tokens │ │• Auto-refresh │ │• Method checks │ │
│ │• Session │ │ tracking │ │• Scope validation│ │
│ │ expiration │ │ │ │• Per IETF 6.2.4 │ │
│ └───────────────┘ └─────────────────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ AuthlibBackend │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ Authlib (Dependency) │ │ │
│ │ │ • OAuth 2.0/2.1 protocol implementation │ │ │
│ │ │ • PKCE (RFC 7636) │ │ │
│ │ │ • Token exchange & refresh │ │ │
│ │ │ • OpenID Connect │ │ │
│ │ │ • 20+ provider configurations │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Provider Configs │ │
│ │ • Google • Azure AD • GitHub • Custom OIDC │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘
How BFFAuth Wraps Authlib
BFFAuth is a wrapper library that adds BFF security layers on top of Authlib, the most popular Python OAuth library (5K+ GitHub stars).
Why Wrap Authlib Instead of Reimplementing?
════════════════════════════════════════════
┌────────────────────────────────────────────────────────────────────────────┐
│ │
│ BFFAuth (Your Code) Authlib (Battle-tested) │
│ ══════════════════ ══════════════════════ │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ BFF Security Layer │ │ OAuth Protocol │ │
│ │ ────────────────────│ │ ────────────────────│ │
│ │ • Session cookies │ wraps │ • Auth code flow │ │
│ │ • Token encryption │◄─────────────►│ • PKCE (RFC 7636) │ │
│ │ • CSRF protection │ │ • Token refresh │ │
│ │ • Allowlist │ │ • OIDC support │ │
│ │ (UNIQUE VALUE) │ │ • 20+ providers │ │
│ └──────────────────────┘ │ (5K+ stars, tested) │ │
│ └──────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────┘
Benefits:
✅ Battle-tested OAuth core (Authlib)
✅ Unique BFF security layer (BFFAuth)
✅ Rich feature set (OIDC, introspection, 20+ providers)
✅ Community trust and security audits
✅ Focused scope - we only maintain BFF logic
OAuth Flows with BFF
Flow A: User Login (SSO)
User Login Flow
═══════════════
┌─────────┐ ┌─────────────┐ ┌───────────────┐ ┌──────────┐
│ Browser │ │ BFF │ │ BFFAuth │ │ Provider │
│ (SPA) │ │ (Your │ │ Library │ │ (Google) │
│ │ │ Server) │ │ │ │ │
└────┬────┘ └──────┬──────┘ └───────┬───────┘ └────┬─────┘
│ │ │ │
│ 1. GET /login │ │ │
│─────────────────►│ │ │
│ │ │ │
│ │ 2. start_authorization() │
│ │────────────────────►│ │
│ │ │ │
│ │ 3. Create session, │ │
│ │ store PKCE │ │
│ │◄────────────────────│ │
│ │ Return auth_url │ │
│ │ │ │
│ 4. Redirect to │ │ │
│ auth_url │ │ │
│◄─────────────────│ │ │
│ │ │ │
│ 5. User authenticates at provider │ │
│────────────────────────────────────────────────────────────►│
│ │ │ │
│ 6. Redirect with code + state │ │
│◄────────────────────────────────────────────────────────────│
│ │ │ │
│ 7. GET /callback?code=...&state=... │ │
│─────────────────►│ │ │
│ │ │ │
│ │ 8. handle_callback()│ │
│ │────────────────────►│ │
│ │ │ 9. Exchange code │
│ │ │ for tokens │
│ │ │───────────────────►│
│ │ │◄───────────────────│
│ │ │ │
│ │ 10. Tokens stored │ │
│ │ in encrypted │ │
│ │ vault │ │
│ │◄────────────────────│ │
│ │ │ │
│ 11. Set-Cookie: │ │ │
│ __Host-session=... │ │
│ (Secure, HttpOnly, SameSite=Strict)│ │
│◄─────────────────│ │ │
│ │ │ │
│ ✅ User logged in │ │
│ ✅ NO tokens in browser │ │
│ │ │ │
Flow B: API Request (Token Injection)
API Request Flow (Browser → BFF → Resource Server)
══════════════════════════════════════════════════
┌─────────┐ ┌─────────────┐ ┌───────────────┐ ┌──────────┐
│ Browser │ │ BFF │ │ BFFAuth │ │ API │
│ (SPA) │ │ (Your │ │ Library │ │ (Google) │
│ │ │ Server) │ │ │ │ │
└────┬────┘ └──────┬──────┘ └───────┬───────┘ └────┬─────┘
│ │ │ │
│ 1. POST /api/search │ │
│ Cookie: __Host-session=... │ │
│ Body: { query: "..." } │ │
│─────────────────►│ │ │
│ │ │ │
│ │ 2. get_access_token() │
│ │────────────────────►│ │
│ │ │ │
│ │ 3. Validate session │ │
│ │ Decrypt token │ │
│ │ Auto-refresh if │ │
│ │ needed │ │
│ │◄────────────────────│ │
│ │ Return token │ │
│ │ │ │
│ │ 4. Validate URL against allowlist │
│ │ │ │
│ │ 5. Call API with token │
│ │ Authorization: Bearer <token> │
│ │─────────────────────────────────────────►│
│ │◄─────────────────────────────────────────│
│ │ │ │
│ 6. Return results│ │ │
│ (NO token │ │ │
│ exposed) │ │ │
│◄─────────────────│ │ │
│ │ │ │
Installation
pip install bffauth
With framework integrations:
pip install bffauth[fastapi] # FastAPI integration
pip install bffauth[flask] # Flask integration
pip install bffauth[all] # All frameworks
Quick Start
from bffauth import BFFOAuthHandler, OAuthProvider
from bffauth.providers import create_google_config
from cryptography.fernet import Fernet
# Generate encryption key (store securely in production!)
encryption_key = Fernet.generate_key().decode()
# Create handler
handler = BFFOAuthHandler(
encryption_key=encryption_key,
redirect_base_uri="https://app.example.com",
)
# Register OAuth provider
handler.register_provider(create_google_config(
client_id="your-google-client-id",
client_secret="your-google-client-secret",
))
# Start OAuth flow
async def login():
auth_url, session = await handler.start_authorization(OAuthProvider.GOOGLE)
# Redirect user to auth_url
# Set session cookie with session.session_id
return auth_url, session
# Handle callback
async def callback(session_id: str, code: str, state: str):
result = await handler.handle_callback(
session_id=session_id,
code=code,
state=state,
)
# User is now authenticated
return result.id_token_claims
# Get token for API calls (server-side only!)
async def call_api(session_id: str):
token = await handler.get_access_token(session_id, OAuthProvider.GOOGLE)
# Use token to call Google APIs - token NEVER sent to browser
return token
Supported Providers
from bffauth.providers import create_google_config, GOOGLE_SCOPES
config = create_google_config(
client_id="...",
client_secret="...",
scope=f"openid email {GOOGLE_SCOPES['calendar_readonly']}",
)
Microsoft Azure AD / Entra ID
from bffauth.providers import create_azure_config, AZURE_SCOPES
# Multi-tenant
config = create_azure_config(
client_id="...",
client_secret="...",
tenant_id="common",
scope=f"openid email {AZURE_SCOPES['user_read']}",
)
# Single-tenant
config = create_azure_config(
client_id="...",
client_secret="...",
tenant_id="your-tenant-id",
)
GitHub
from bffauth.providers import create_github_config, GITHUB_SCOPES
config = create_github_config(
client_id="...",
client_secret="...",
scope=f"{GITHUB_SCOPES['user_read']} {GITHUB_SCOPES['repo']}",
)
Custom OIDC Provider
from bffauth.providers import create_custom_config
config = create_custom_config(
client_id="...",
client_secret="...",
authorization_endpoint="https://idp.example.com/authorize",
token_endpoint="https://idp.example.com/token",
userinfo_endpoint="https://idp.example.com/userinfo",
)
# Or auto-discover from OIDC metadata
from bffauth.providers import create_oidc_config_from_discovery_async
config = await create_oidc_config_from_discovery_async(
client_id="...",
client_secret="...",
discovery_url="https://idp.example.com/.well-known/openid-configuration",
)
Security Features
Resource Server Allowlist (IETF 6.2.4)
Per IETF security requirements, BFF must validate that tokens are only sent to approved APIs:
from bffauth import AllowlistValidator, AllowedResource, OAuthProvider
allowlist = AllowlistValidator([
AllowedResource(
name="google_apis",
url_pattern=r"^https://.*\.googleapis\.com/.*$",
provider=OAuthProvider.GOOGLE,
),
AllowedResource(
name="internal_api",
url_pattern=r"^https://api\.example\.com/.*$",
allowed_methods=["GET", "POST"],
),
])
handler = BFFOAuthHandler(
encryption_key=key,
allowlist=allowlist,
)
Cookie Security
BFFAuth uses __Host- prefixed cookies by default:
| Attribute | Value | Purpose |
|---|---|---|
HttpOnly |
true | Not accessible to JavaScript |
Secure |
true | Only sent over HTTPS |
SameSite |
Strict | CSRF protection |
Path |
/ |
Required for __Host- prefix |
Token Encryption
Tokens are encrypted at rest using Fernet (AES-128-CBC + HMAC-SHA256):
from cryptography.fernet import Fernet
# Generate key once, store in secrets manager
key = Fernet.generate_key()
# NEVER commit to version control
CSRF Protection
Every session includes a CSRF token for form submissions:
# Get CSRF token for forms
session = await handler.get_session(session_id)
csrf_token = session.csrf_token
# Validate on form submission
await handler.validate_csrf(session_id, submitted_token)
Production Storage
Default in-memory storage is for development only. For production, implement storage protocols:
from bffauth import SessionStorageProtocol, TokenVaultStorageProtocol
class RedisSessionStorage(SessionStorageProtocol):
async def get(self, session_id: str) -> Optional[SessionData]:
...
async def set(self, session: SessionData) -> None:
...
async def delete(self, session_id: str) -> bool:
...
class RedisVaultStorage(TokenVaultStorageProtocol):
async def get(self, session_id: str) -> Optional[bytes]:
...
async def set(self, session_id: str, data: bytes) -> None:
...
handler = BFFOAuthHandler(
encryption_key=key,
session_storage=RedisSessionStorage(redis_client),
token_storage=RedisVaultStorage(redis_client),
)
API Reference
BFFOAuthHandler
Main handler class for BFF authentication.
| Method | Description |
|---|---|
register_provider(config) |
Register OAuth provider |
start_authorization(provider) |
Start OAuth flow, returns (url, session) |
handle_callback(session_id, code, state) |
Handle OAuth callback |
get_access_token(session_id, provider) |
Get valid access token |
logout(session_id, provider?) |
Logout user |
get_session(session_id) |
Get session data |
validate_csrf(session_id, token) |
Validate CSRF token |
Exceptions
| Exception | Description |
|---|---|
BFFAuthError |
Base exception for all BFFAuth errors |
AuthorizationError |
OAuth authorization failed |
TokenNotFoundError |
No token for provider |
TokenExpiredError |
Token expired, refresh needed |
SessionNotFoundError |
Session not found or expired |
CSRFValidationError |
CSRF token mismatch |
ResourceNotAllowedError |
URL not in allowlist |
Competitive Analysis
Feature Comparison
BFFAuth provides feature parity with commercial and open-source BFF implementations:
| Feature | BFFAuth | Duende BFF | Curity | michaelvl |
|---|---|---|---|---|
| Core OAuth | ||||
| OAuth 2.0/2.1 | ✅ | ✅ | ✅ | ✅ |
| PKCE (SHA256) | ✅ | ✅ | ✅ | ✅ |
| Multi-provider | ✅ | ✅ | ✅ | ❌ |
| Token refresh | ✅ | ✅ | ✅ | ✅ |
| Session Security | ||||
__Host- cookies |
✅ | ✅ | ✅ | ✅ |
| HttpOnly, Secure | ✅ | ✅ | ✅ | ✅ |
| SameSite | ✅ | ✅ | ✅ | ✅ |
| CSRF protection | ✅ | ✅ | ✅ | ✅ |
| Token Security | ||||
| Encrypted at rest | ✅ | ✅ | ✅ | ❌ |
| Allowlist validation | ✅ | ✅ | ✅ | ❌ |
| Token rotation | ✅ | ✅ | ✅ | ❌ |
| Advanced | ||||
| Back-channel logout | ⏳ | ✅ | ✅ | ❌ |
| Silent login | ⏳ | ✅ | ✅ | ❌ |
| API proxy | ⏳ | ✅ | ✅ | ❌ |
| OpenTelemetry | ⏳ | ✅ | ✅ | ❌ |
| Financial-grade (FAPI) | ⏳ | ❌ | ✅ | ❌ |
BFFAuth Advantages
| Advantage | Description |
|---|---|
| Python-native | First-class Python support with async/await, type hints, and Pydantic models |
| Authlib foundation | Built on battle-tested OAuth library (5K+ GitHub stars) |
| Protocol-based | Fully swappable components via Python Protocols |
| Open source | Apache 2.0 license (Duende is commercial) |
| Framework agnostic | Works with FastAPI, Flask, Django, or any ASGI/WSGI framework |
| ADK integration | Native support for Google Agent Development Kit |
Planned Features (Roadmap)
Features planned for upcoming releases:
- v0.2.0: Back-channel logout, Redis storage, sliding sessions
- v0.3.0: API proxy, silent login, OpenTelemetry
- v1.0.0: Full production hardening, database storage
- v1.1.0: Financial-grade OIDC (PAR, JARM, mTLS)
See ROADMAP.md for detailed timeline.
References
IETF Standards
- IETF draft-ietf-oauth-browser-based-apps - OAuth 2.0 for Browser-Based Applications (BFF is the #1 recommended pattern, Section 6.2)
- RFC 7636 - Proof Key for Code Exchange (PKCE)
- RFC 6749 - OAuth 2.0 Authorization Framework
Dependencies
- Authlib - The OAuth library powering BFFAuth's OAuth backend
- Cryptography - Fernet encryption for token storage
- Pydantic - Data validation and models
Related Projects
| Project | Language | License | Description |
|---|---|---|---|
| Duende BFF | .NET | Commercial | Full-featured BFF for ASP.NET |
| Curity Token Handler | Various | Commercial | Financial-grade OAuth proxy |
| michaelvl/oidc-oauth2-bff | Go | MIT | Lightweight OIDC BFF |
| FusionAuth | Various | Commercial | Identity platform with BFF support |
BFFAuth is the first open-source Python library implementing the complete BFF pattern per IETF specifications.
Contributing
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
License
Apache License 2.0 - see LICENSE for details.
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
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 bffauth-0.2.0.tar.gz.
File metadata
- Download URL: bffauth-0.2.0.tar.gz
- Upload date:
- Size: 106.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19f26c69b962068d712b97732ac5cdcf99147075d7e24a32a6335ef46f40f226
|
|
| MD5 |
61e30e1cb71f612d16ba82c7b5e79bf3
|
|
| BLAKE2b-256 |
45fa9135fda1999b174fe37f2bfb8f382827cdc940492ce33f813900241a57a3
|
File details
Details for the file bffauth-0.2.0-py3-none-any.whl.
File metadata
- Download URL: bffauth-0.2.0-py3-none-any.whl
- Upload date:
- Size: 45.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9be15c071d940cfcb5e340a04576883682b5ca4617bcad027b6d7df5c0c7ddf6
|
|
| MD5 |
375eee638ba790a2b506e7e1f3dea9a9
|
|
| BLAKE2b-256 |
b2666f978998d8d8ca8463b4d95fc51e594f6d1ff244a28107869d219ecb52ec
|