Skip to main content

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.

PyPI Python Downloads License

CI codecov Checked with mypy

Ruff Code style: black pre-commit

OpenSSF Scorecard

Table of Contents


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

  1. XSS Protection: Even if an attacker injects malicious JavaScript, they cannot steal OAuth tokens because tokens never reach the browser.

  2. Simplified Frontend: Your SPA doesn't need to manage tokens, refresh logic, or handle OAuth complexity.

  3. Centralized Security: All security logic (token validation, refresh, allowlist) is in one place on the server.

  4. Multi-Provider Support: Easily connect multiple OAuth providers (Google, Azure, GitHub) with tokens stored in a unified vault.

  5. 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
Google ✅ 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 optional dependencies:

pip install bffauth[redis]    # Redis storage backend
pip install bffauth[fastapi]  # FastAPI integration (planned)
pip install bffauth[flask]    # Flask integration (planned)
pip install bffauth[all]      # All optional dependencies

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

Google

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, use the built-in Redis storage:

pip install bffauth[redis]
import redis.asyncio as redis
from bffauth import BFFOAuthHandler
from bffauth.storage import RedisSessionStorage, RedisVaultStorage

# Create Redis client
redis_client = redis.Redis.from_url("redis://localhost:6379/0")

# Use built-in Redis storage
handler = BFFOAuthHandler(
    encryption_key=key,
    session_storage=RedisSessionStorage(redis_client),
    token_storage=RedisVaultStorage(redis_client),
)

Redis storage features:

  • Automatic TTL-based expiration aligned with session lifetimes
  • Sliding session expiration support
  • Custom key prefixes for multi-tenant deployments
  • Session lookup by user_id for back-channel logout

For custom storage backends, implement the storage protocols:

from bffauth import SessionStorageProtocol, VaultStorageProtocol

class CustomSessionStorage(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: ...
    async def find_by_user_id(self, user_id: str) -> list[SessionData]: ...

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 (local)
get_logout_url(session_id, provider) Get IdP logout URL (RP-Initiated Logout)
handle_backchannel_logout(logout_token) Process IdP back-channel logout
get_session(session_id) Get session data
refresh_session(session_id) Extend session (sliding expiration)
validate_csrf(session_id, token) Validate CSRF token (form)
validate_csrf_header(session_id, header) Validate CSRF header (API)

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
LogoutTokenError Back-channel logout token invalid
StateValidationError OAuth state parameter mismatch

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
RP-Initiated logout
Sliding sessions
Redis storage
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

Release History

Version Status Key Features
v0.1.0 ✅ Released Core OAuth 2.1, PKCE, session management, token vault
v0.2.0 ✅ Released Back-channel logout, RP-Initiated logout, Redis storage, sliding sessions, CSRF headers
v0.2.1 ✅ Released Documentation updates, release process improvements
v0.2.2 ✅ Released Trust & quality badges, OpenSSF Scorecard, CI/CD improvements
v0.2.3 ✅ Released CI test fix (redis import), code formatting

Planned Features (Roadmap)

Features planned for upcoming releases:

  • 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

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

bffauth-0.2.3.tar.gz (114.1 kB view details)

Uploaded Source

Built Distribution

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

bffauth-0.2.3-py3-none-any.whl (46.6 kB view details)

Uploaded Python 3

File details

Details for the file bffauth-0.2.3.tar.gz.

File metadata

  • Download URL: bffauth-0.2.3.tar.gz
  • Upload date:
  • Size: 114.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for bffauth-0.2.3.tar.gz
Algorithm Hash digest
SHA256 9975754be187f70b4a6cdcc4c92703f0e9163064d5e8d280b9df6f6c3b69d6dc
MD5 de9c4c87c8c57040ee2fc82098c8977c
BLAKE2b-256 b5c2857160a851ec91bc82ea649045dff4885b97a8a3136449f5f01154cf8afa

See more details on using hashes here.

File details

Details for the file bffauth-0.2.3-py3-none-any.whl.

File metadata

  • Download URL: bffauth-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 46.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for bffauth-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 e212af709278e50b59adbfe9075cc946947fae57c084a80edf4d2e14bf9c99f1
MD5 f9d3ee0a694e7a69e7a11ffdb7f56a5d
BLAKE2b-256 a706c6a116d30f0b257e5b8a4a2ef51156118b338321af95d086611a69c8c2c9

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