# How to Add Authentication to a Python Backend

**How do I add authentication to a Python backend?**

A Python backend authenticates by verifying a signed token the frontend attaches to every request — it does not render sign-in forms, run OAuth handshakes, or store passwords. The recommended 2026 stack is Clerk's official [`clerk-backend-api`](https://pypi.org/project/clerk-backend-api/) for [token](https://clerk.com/glossary.md#token) verification on the Python side, paired with any Clerk frontend SDK (React, Next.js, Expo, Vanilla JS, iOS, Android) for the sign-in UI. The walkthrough below covers FastAPI and Flask in depth, Django briefly, and the React call-site pattern for completeness.

[`authenticate_request()`](https://clerk.com/docs/reference/backend/authenticate-request.md) accepts any request object that exposes a `headers` mapping ([source](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/types.py)) — that covers FastAPI `Request`, Flask `request`, Django `HttpRequest`, Starlette `Request`, and Sanic `Request` directly. Raw ASGI scopes or other shapes without a `headers` attribute need a thin adapter.

## Quick reference

| Piece                                                                                        | What it does                                                  | Where it runs               |
| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------- |
| Frontend SDK (`@clerk/clerk-react`, `@clerk/nextjs`, etc.)                                   | Collects credentials, handles OAuth, mints a session token    | Browser / mobile            |
| [`clerk-backend-api`](https://pypi.org/project/clerk-backend-api/)                           | Verifies the token, reads claims, calls the Clerk Backend API | Your Python server          |
| Session token (JWT)                                                                          | Signed proof the user is signed in; 60-second lifetime        | Sent on every request       |
| [JWKS](https://clerk.com/glossary.md#json-web-token) / `CLERK_JWT_KEY`                       | The public key used to verify signatures                      | Cached on your server       |
| [`authenticate_request()`](https://clerk.com/docs/reference/backend/authenticate-request.md) | Reads the token, verifies it, returns the claims              | Per-request in your handler |

**Jump to your framework:**

1. [FastAPI integration](#adding-clerk-authentication-to-fastapi)
2. [Flask integration](#adding-clerk-authentication-to-flask)
3. [Django / DRF pattern](#brief-using-clerk-with-django--drf)
4. [React frontend call-site](#integrating-with-a-react-frontend)

## A minimal FastAPI example

Here's the smallest useful protected endpoint. Full code with project scaffolding and error handling appears later.

```python
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, Request
from clerk_backend_api import authenticate_request, AuthenticateRequestOptions
import os

app = FastAPI()

def require_user(request: Request) -> str:
    state = authenticate_request(
        request,
        AuthenticateRequestOptions(
            secret_key=os.environ["CLERK_SECRET_KEY"],
            jwt_key=os.environ.get("CLERK_JWT_KEY"),
            authorized_parties=["http://localhost:3000"],
            accepts_token=["session_token"],
        ),
    )
    if not state.is_signed_in:
        raise HTTPException(status_code=401, detail=state.reason)
    return state.payload["sub"]

@app.get("/api/me")
def me(user_id: Annotated[str, Depends(require_user)]):
    return {"user_id": user_id}
```

That's everything. The frontend calls `fetch('/api/me', { headers: { Authorization: 'Bearer <token>' } })` and Clerk's SDK verifies the signature locally against `CLERK_JWT_KEY`. No network call per request, no session storage, no password hashing. The only thing the backend has to know how to do is verify a signature.

## Who this guide is for

This article is for three readers:

1. **Python developers building a FastAPI, Flask, or Django backend** who need to protect API endpoints and don't want to write [JWT](https://clerk.com/glossary.md#json-web-token) verification from scratch.
2. **React or Next.js developers** who already use Clerk on the frontend and need the backend half. You're comfortable with Clerk's React components but haven't touched the Python SDK yet.
3. **Developers new to [authentication](https://clerk.com/glossary.md#authentication)** who want a production-ready setup without rolling their own. You've heard the words JWT, [OAuth](https://clerk.com/glossary.md#oauth), and [SSO](https://clerk.com/glossary/single-sign-on-sso.md), but you don't want to build any of them.

**Assumptions:**

1. Python 3.10 or higher. The current `clerk-backend-api` (v5.0.6) requires `>=3.10` per its [`pyproject.toml`](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/pyproject.toml). If you're on 3.8 or 3.9, upgrade Python (recommended) or pin `clerk-backend-api<3` (discouraged, predates the current API).
2. Familiarity with HTTP and at least one of FastAPI or Flask.
3. A package manager: `uv`, `pip`, or `poetry`. Examples use `uv` first, `pip` second.
4. A frontend that can acquire a Clerk session token: React, Next.js, Expo, mobile, or a Clerk-aware API client.

**How to use this guide.** Sections 3 through 5 (mental model, options, setup) apply to any Python backend. Read them once. Then skip to your framework: Section 6 for FastAPI, Section 7 for Flask, Section 8 for Django/DRF. The React integration, advanced SDK features, production deployment notes, and comparison table are framework-agnostic and come after. The FAQ is a scannable index for when you hit a specific error or concept.

## How Python backend authentication actually works

If you take one thing from this article, take this: the frontend acquires the token, the backend verifies it. That's the whole model. Everything else is plumbing.

### Frontend vs. backend responsibilities

The frontend is where the human is. It collects passwords (or a passkey prompt, or an OAuth redirect, or an [MFA](https://clerk.com/glossary.md#multi-factor-authentication-mfa) code), hands those to the auth provider's Frontend API, and receives a signed session token back. It then attaches that token to every API call, typically as `Authorization: Bearer <token>` or a `__session` cookie.

The backend never sees the password. The backend never runs the OAuth dance. The backend's only job is to verify that the token is genuine, fresh, and from a party it trusts, then read the claims (who is this user? what org are they in? what permissions do they have?) and [authorize](https://clerk.com/glossary.md#authorization) the request.

A full request lifecycle with Clerk looks like this:

1. Browser loads your app.
2. Clerk's frontend SDK talks to the [Clerk Frontend API](https://clerk.com/glossary.md#frontend-api) and mints a session.
3. User makes an action that calls your Python backend.
4. Frontend fetches the short-lived session token via `getToken()` and attaches it to the request.
5. Python backend receives the request, passes it to `authenticate_request()`.
6. `authenticate_request()` verifies the RS256 signature using the cached public key, checks expiry, checks the `azp` claim against your allow-list, returns the claims.
7. Your handler authorizes the action and returns a response.

There's no handshake to Clerk on that critical path. With `jwt_key` (networkless mode), verification is a local RS256 signature check — no network round-trip to Clerk on verification. The networked fallback makes a one-time JWKS fetch per process per `kid`, cached in memory, and verifies locally from then on. See the [`authenticateRequest` reference](https://clerk.com/docs/reference/backend/authenticate-request.md) for the exact behavior.

### Five misconceptions worth clearing up first

**"Python can handle sign-up and sign-in directly."** Not in modern auth, no. Sign-up and sign-in involve OAuth redirects, passkey WebAuthn flows, MFA challenges, session refresh with rolling tokens — all of which live in the browser or mobile client. A Python framework can render a form, but the moment you add Google login, passkeys, or magic links, you've moved that flow into the browser anyway. A real community example: [clerk/clerk-sdk-python#59](https://github.com/clerk/clerk-sdk-python/issues/59) asks for a CLI sign-in helper, which isn't how Clerk's SDK works.

**"Clerk's Python SDK has the same UI components as the React SDK."** It does not. `clerk-backend-api` is backend-only. It verifies tokens, reads user data, manages sessions, and handles webhooks. Components like `<SignIn />`, `<UserButton />`, and `<OrganizationSwitcher />` ship only in the frontend SDKs (`@clerk/clerk-react`, `@clerk/nextjs`, `@clerk/expo`, etc.). The pairing pattern is simple: use Clerk's frontend SDK on the client, `clerk-backend-api` on the Python server.

**"I need to store passwords or session tokens in my database."** No. Clerk stores users, passwords, active sessions, MFA factors, OAuth linkages, and impersonation audit trails. Your Python database only stores your application data, keyed by the Clerk user ID (`user_xxx...`). Clerk's [syncing guide](https://clerk.com/docs/guides/development/webhooks/syncing.md) documents the canonical pattern: when a `user.created` webhook arrives, insert a row with `clerk_id=data["id"]` and nothing else password-adjacent.

**"JWT verification requires calling the auth provider on every request."** No. RS256 [JWTs](https://clerk.com/glossary.md#json-web-token) are asymmetric. The issuer (Clerk) signs with a private key. You verify with the public key. If you pass `jwt_key=` into `AuthenticateRequestOptions` with the PEM-formatted public key, verification is a local math operation — zero network calls. The networked fallback fetches [JWKS](https://clerk.com/glossary.md#json-web-token) from `https://api.clerk.com/v1/jwks` once per process per `kid` and caches it in memory. Either way, you are not round-tripping to Clerk on every request.

**"I have to manage CORS, cookies, and tokens manually."** The SDK reads the token automatically. It checks `Authorization: Bearer <token>` first, then the `__session` cookie as a fallback. FastAPI's `Request` and Flask's `request` both satisfy the `Requestish` structural [protocol](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/types.py) the SDK expects — no wrapping, no adapter. You do have to configure [CORS](https://fastapi.tiangolo.com/tutorial/cors/) on your Python side (covered in Section 11), but token extraction is not something you write.

### Token formats a Python backend sees

Clerk emits several token types. Most Python backends only handle the first one, but it's worth knowing the rest exist.

| Token type         | Prefix | Transport                                     | Typical use                                 |
| ------------------ | ------ | --------------------------------------------- | ------------------------------------------- |
| Session JWT        | none   | `__session` cookie or `Authorization: Bearer` | User-initiated API calls                    |
| API key            | `ak_`  | `Authorization: Bearer`                       | User-created programmatic access            |
| M2M token (opaque) | `m2m_` | `Authorization: Bearer`                       | Service-to-service, revocable               |
| M2M token (JWT)    | `mt_`  | `Authorization: Bearer`                       | Service-to-service, networkless             |
| OAuth access token | `oat_` | `Authorization: Bearer`                       | Third-party apps acting on behalf of a user |

A note on defaults that trips up teams migrating from the Node SDK. In the current Python SDK (5.0.6), the `accepts_token` field on `AuthenticateRequestOptions` [defaults to `['any']`](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/types.py) — every token type above is accepted by default. Clerk's canonical [`authenticateRequest` reference](https://clerk.com/docs/reference/backend/authenticate-request.md) documents the JS/Node SDK default as `'session_token'`, and the Node SDK enforces that default. For parity with the documented default and defense in depth, pass `accepts_token=['session_token']` explicitly on session-only endpoints; restrict M2M-only endpoints with `accepts_token=['m2m_token']` or combine types with `accepts_token=['session_token', 'api_key']`. The full token [type definition](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/types.py) lives in the SDK source.

One note worth keeping in your pocket: the default session token format is v2. Clerk deprecated [v1 on 2025-04-14](https://clerk.com/changelog/2025-04-14-session-token-jwt-v2.md); **in the raw JWT**, org claims that used to be flat (`org_id`, `org_role`) are now nested under an `o` object (`o.id`, `o.rol`, `o.per`). The Python SDK smooths this over by re-surfacing the flat names on `payload` after `authenticate_request()` — `payload["org_id"]`, `payload["org_role"]`, `payload["org_slug"]`, plus the decoded `payload["org_permissions"]` — so application code can read them directly without touching the nested `payload["o"]` dict. Copy-pasted code from mid-2024 tutorials that reached into the (now-removed) top-level `org_id` / `org_role` JWT claims will still work via `payload[...]` because of that SDK-level enrichment, but anything that reads straight from the JSON-decoded token still has to go through `o.*`.

## Your options for Python backend authentication

There are three realistic paths. Most teams pick option 3 once they count the real cost of the others.

### Option 1: Roll your own with PyJWT + pwdlib

You can, in theory, build authentication yourself. You'd hash passwords (Argon2id is the [OWASP 2024 recommendation](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)), mint and verify JWTs, rotate signing keys, send verification emails, handle password resets, implement MFA, integrate [passkeys](https://clerk.com/glossary.md#passkeys) via WebAuthn, wire up OAuth clients for every social provider, rate-limit login endpoints, detect credential stuffing, and commit to a SOC 2 audit cycle.

[WorkOS's 2026 Python authentication guide](https://workos.com/blog/python-authentication-guide-2026) estimates 2–6 weeks for an MVP, 2–3 months for a production-ready system, and $50,000–$200,000 per year for SOC 2 compliance alone. That's before passkeys or organizations.

A critical note on libraries if you're reading older tutorials: the classic FastAPI stack was [`python-jose`](https://pypi.org/project/python-jose/) + `passlib`. Both are effectively unmaintained. `python-jose` carries [CVE-2024-33663](https://nvd.nist.gov/vuln/detail/CVE-2024-33663), an algorithm confusion vulnerability with a CVSS score of 6.5. `passlib`'s last release was [October 2020](https://pypi.org/project/passlib/) and it breaks with `bcrypt` ≥5.0. FastAPI itself officially migrated to [`PyJWT`](https://pypi.org/project/PyJWT/) and [`pwdlib`](https://pypi.org/project/pwdlib/) with Argon2 support in [May 2024](https://github.com/fastapi/fastapi/pull/11589). If you're going to roll your own, use those instead.

Where rolling your own fits: learning exercises, internal tools with no external users, or cases where auth is literally your product.

### Option 2: Framework extensions

[Flask-Login](https://pypi.org/project/Flask-Login/) is session-cookie based. It doesn't fit a stateless bearer-token API where the frontend and backend are decoupled (a React SPA calling a Python API). The last release was [0.6.3 in October 2023](https://pypi.org/project/Flask-Login/).

[FastAPI Users](https://github.com/fastapi-users/fastapi-users) is in maintenance mode. The maintainers have publicly stated they're only accepting security and dependency updates; no new features. A successor project is discussed in the repo but isn't shipping.

[django-allauth](https://docs.allauth.org/) (currently 65.16.0) is the one framework extension that's still actively maintained and feature-complete. It includes MFA, WebAuthn, 100+ social providers, and email verification. It fits Django apps with server-rendered pages. It does not fit decoupled SPA + API architectures because it's built around Django's session middleware.

None of these give you passkeys plus MFA plus organizations plus webhooks plus prebuilt React UI in one package.

### Option 3: Managed auth providers

This is the category [Clerk](https://clerk.com/), Auth0, Supabase Auth, Firebase Auth, and AWS Cognito all live in. What they share: hosted user database, prebuilt frontend flows, JWT-based backend verification, [SOC 2](https://clerk.com/glossary.md#soc-2) compliance, passkey support.

Where they differ matters a lot for Python specifically:

1. First-party Python SDK maturity and release cadence
2. Dedicated FastAPI / Flask / Django helpers
3. Passkey and MFA availability on the free or low tiers
4. Organizations / [multi-tenant](https://clerk.com/glossary/multi-tenancy.md) B2B support
5. Networkless JWT verification without DIY JWKS management
6. Free-tier unit ([MRU](https://clerk.com/glossary.md#monthly-retained-users-mrus) vs [MAU](https://clerk.com/glossary.md#monthly-active-users-maus)) and allowance
7. Transparent pricing

Why Clerk is the focus of this guide: first-party [`clerk-backend-api`](https://github.com/clerk/clerk-sdk-python) with monthly releases (5.0.6 in March 2026), strong [organizations](https://clerk.com/glossary.md#organizations) / B2B support, prebuilt React / Next.js / Expo frontends that match the Python backend one-to-one, passkeys included in the Pro plan, and [transparent MRU-based pricing](https://clerk.com/pricing). Python-specific helpers ship in the same SDK: networkless verification, webhook handling, M2M tokens, and organization management are all one import away.

Full comparison table appears in [Section 12](#clerk-vs-other-python-authentication-options). Short version: for a new Python API paired with a modern frontend, Clerk is the path with the fewest decisions to make.

## Setting up Clerk for a Python backend

Before you touch FastAPI or Flask specifics, get these three things in place: a Clerk application, your keys, and a sane environment config.

### Prerequisites

- [ ] Python 3.10 or higher. Confirm with `python --version`.
- [ ] A Clerk account. Sign up at [clerk.com](https://clerk.com/).
- [ ] Any Clerk-supported frontend — React, Next.js, Expo, vanilla JS, iOS, Android — capable of obtaining a session token.
- [ ] `uv`, `pip`, or `poetry`. Examples use `uv` first.

### Create a Clerk application and collect your keys

Create an application in the [Clerk Dashboard](https://dashboard.clerk.com/). Enable at least one sign-in method (email + password is enough to start; add passkeys, social OAuth, or magic links later).

You'll collect four pieces of configuration:

1. **Publishable key** (`pk_test_...` or `pk_live_...`) — frontend. Safe to ship in browser bundles. Identifies your Clerk application.
2. **[Secret key](https://clerk.com/glossary.md#secret-key)** (`sk_test_...` or `sk_live_...`) — backend. Never ships to the client. Authorizes Backend API calls.
3. **JWT public key** (PEM) — backend. Used for networkless token verification. Find this at **Dashboard → API keys → scroll to "Advanced" → "Show JWT public key"**, which reveals a PEM-formatted `-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----` block. Confirmed against the [Manual JWT verification guide](https://clerk.com/docs/guides/sessions/manual-jwt-verification.md) and the [`authenticateRequest` reference](https://clerk.com/docs/reference/backend/authenticate-request.md).
4. **Webhook signing secret** (`whsec_...`) — backend. Only needed when you add webhooks. We'll cover this in Section 10.

Development vs. production keys matter. Use `pk_test_` and `sk_test_` locally; switch to `pk_live_` and `sk_live_` for production deploys. Never share a secret key with the frontend and never commit it to source control.

### Install the Clerk Python SDK

```bash
uv add clerk-backend-api
```

Equivalent with `pip`:

```bash
pip install clerk-backend-api
```

Or `poetry`:

```bash
poetry add clerk-backend-api
```

Confirm the install: `python -c "import clerk_backend_api; print(clerk_backend_api.__version__)"` should show 5.0.6 or later. The package is auto-generated from Clerk's OpenAPI spec via [Speakeasy](https://www.speakeasy.com/), and sync and async variants live on the same `Clerk` class — there's no separate `AsyncClerk`. See [sdk.py](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/sdk.py) if you're curious about the structure.

Source: [`clerk-backend-api` on PyPI](https://pypi.org/project/clerk-backend-api/), [GitHub repo](https://github.com/clerk/clerk-sdk-python).

### Environment configuration

Create a `.env` at the project root:

```env
CLERK_SECRET_KEY=sk_test_...
CLERK_JWT_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
CLERK_AUTHORIZED_PARTIES=http://localhost:3000,https://yourapp.com
CLERK_WEBHOOK_SIGNING_SECRET=whsec_...
```

The `CLERK_JWT_KEY` value is the PEM block copied from the Dashboard, with real newlines replaced by `\n`. In FastAPI you'll let `pydantic-settings` parse it; in Flask you'll use `python-dotenv`. `http://localhost:3000` and `https://yourapp.com` are placeholders for your real development and production frontend origins — replace both before deploying.

Add `.env` to your `.gitignore`:

```gitignore
.env
.env.*
!.env.example
```

> Clerk **highly recommends** setting `authorized_parties` when authorizing requests. `authenticate_request()` compares the token's `azp` claim against this list; not setting it _can_ open your application to [CSRF](https://clerk.com/glossary.md#cross-site-request-forgery-csrf) attacks — a session token issued for `yourapp.com` could be replayed from `evil.com` on the same device. The [Manual JWT verification guide](https://clerk.com/docs/guides/sessions/manual-jwt-verification.md) says verbatim: _"For better security, it's highly recommended to explicitly set the `authorizedParties` option when authorizing requests. … Not setting this value can open your application to CSRF attacks."_ Configure the list with your real frontend origins before shipping to production.

A common gotcha: `CLERK_AUTHORIZED_PARTIES` is a string when it lands in `os.environ`, but `AuthenticateRequestOptions` expects `list[str]`. Passing the raw string puts the entire comma-joined value in as a single list element, and every request fails with `TOKEN_INVALID_AUTHORIZED_PARTIES`. We handle this properly in the FastAPI (Section 6b) and Flask (Section 7b) configs.

Why `CLERK_JWT_KEY` is a PEM and not the publishable key: the publishable key identifies your Clerk application to the Frontend API. The JWT public key is the RSA public half of the keypair Clerk uses to sign session tokens. It's what you actually verify signatures with. They're different values, not interchangeable. The Clerk docs also match these names in their canonical [environment variables guide](https://clerk.com/docs/guides/development/clerk-environment-variables.md). Note: the archived [`clerk/fastapi-example`](https://github.com/clerk/fastapi-example) uses `CLERK_API_SECRET_KEY` instead of `CLERK_SECRET_KEY`. The docs and every other Clerk SDK use `CLERK_SECRET_KEY`. If you're copy-pasting from the archived example, rename the variable.

### Recommended project structure

Two parallel layouts depending on framework.

FastAPI:

```
app/
  main.py          # FastAPI() + CORS + router registration
  config.py        # Settings(BaseSettings) + get_settings()
  auth.py          # require_auth dependency, require_permission factory
  routers/
    public.py
    protected.py
  webhooks.py      # Clerk webhook endpoint
tests/
.env
pyproject.toml
```

Flask:

```
app/
  __init__.py      # create_app() factory + CORS + blueprint registration
  config.py        # Config class reading from os.environ
  auth.py          # @clerk_required decorator + @require_permission
  routes/
    public.py      # Blueprint
    protected.py   # Blueprint
  webhooks.py      # Blueprint with raw-body handler
tests/
.env
pyproject.toml
```

The exact code for each file appears in the framework sections below. The layout is not load-bearing; use what fits your team.

## Adding Clerk authentication to FastAPI

This is the biggest section. FastAPI's dependency injection system is the idiomatic place for authentication, and the modern `Annotated[X, Depends(dep)]` syntax makes it read cleanly. If you're on an older FastAPI tutorial using `= Depends()` in default parameters, the pattern here is the current one.

### Project setup from scratch

```bash
uv init python-backend-auth
cd python-backend-auth
uv add fastapi "uvicorn[standard]" clerk-backend-api pydantic-settings python-dotenv
```

Minimal `app/main.py`:

```python
from fastapi import FastAPI

app = FastAPI(title="Python Backend Auth")

@app.get("/health")
def health():
    return {"status": "ok"}
```

Run the dev server:

```bash
uv run uvicorn app.main:app --reload --port 8000
```

Hit `http://localhost:8000/health` and you should see `{"status":"ok"}`. Everything else in this section builds on this skeleton.

For production, don't run `uvicorn --reload`. Use Gunicorn as a process manager with Uvicorn workers: `gunicorn -k uvicorn_worker.UvicornWorker app.main:app`. Note the `uvicorn_worker` package (hyphen-to-underscore) — the `uvicorn.workers` stdlib module is deprecated in favor of the [standalone `uvicorn-worker` package](https://pypi.org/project/uvicorn-worker/).

### Configure `pydantic-settings`

Create `app/config.py`:

```python
from functools import lru_cache
from typing import Annotated

from pydantic import field_validator
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict


class Settings(BaseSettings):
    clerk_secret_key: str
    clerk_jwt_key: str | None = None
    clerk_authorized_parties: Annotated[list[str], NoDecode] = []
    clerk_webhook_signing_secret: str | None = None

    @field_validator("clerk_authorized_parties", mode="before")
    @classmethod
    def _split_csv(cls, v: str | list[str]) -> list[str]:
        if isinstance(v, str):
            return [p.strip() for p in v.split(",") if p.strip()]
        return v

    model_config = SettingsConfigDict(env_file=".env", case_sensitive=False)


@lru_cache
def get_settings() -> Settings:
    return Settings()
```

Two details worth understanding here.

First, the `NoDecode` + `field_validator` pair. `pydantic-settings` v2 decodes `list[str]` fields as JSON by default. A plain CSV env value that joins two origins with a comma raises `JSONDecodeError` at startup. `NoDecode` opts that one field out; the validator splits on commas, trims whitespace, drops empties. The alternative is JSON-in-env (`CLERK_AUTHORIZED_PARTIES='["http://localhost:3000"]'`), which is valid but awkward for Docker or Kubernetes. See [pydantic-settings parsing environment variable values](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) and [issue #291](https://github.com/pydantic/pydantic-settings/issues/291) for the underlying behavior.

Second, `@lru_cache`. Reading `.env` once at startup is correct. The cache makes `get_settings()` idempotent and testable: in tests, override with `app.dependency_overrides[get_settings] = lambda: Settings(clerk_secret_key="sk_test_fake", ...)`. Source: [FastAPI settings docs](https://fastapi.tiangolo.com/advanced/settings/).

Pydantic v1 note: the `NoDecode` + `field_validator` + `SettingsConfigDict` syntax above is `pydantic-settings` v2, which extracted `BaseSettings` out of `pydantic` core into a separate package at the [v2.0 release on 2023-06-30](https://github.com/pydantic/pydantic-settings). `pip install pydantic-settings` pulls v2.x today ([current 2.13.3, April 2026](https://pypi.org/project/pydantic-settings/)). Legacy v1 codebases keep `BaseSettings` inside `pydantic` itself, do not have `NoDecode`, and use `@validator("...", pre=True)` with an inner `class Config`. Migration guide: [pydantic migration](https://docs.pydantic.dev/latest/migration/). Active development on v1 ended [2024-06-30](https://docs.pydantic.dev/2.0/migration/), so new projects should be on v2.

### Create the Clerk authentication dependency

This is the subsection to read twice. Everything else builds on it.

Create `app/auth.py`:

```python
from typing import Annotated

from clerk_backend_api import AuthenticateRequestOptions, authenticate_request
from clerk_backend_api.security.types import RequestState
from fastapi import Depends, HTTPException, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from app.config import Settings, get_settings

http_bearer = HTTPBearer(auto_error=False)


def require_auth(
    request: Request,
    settings: Annotated[Settings, Depends(get_settings)],
    _creds: Annotated[HTTPAuthorizationCredentials | None, Depends(http_bearer)] = None,
) -> RequestState:
    state = authenticate_request(
        request,
        AuthenticateRequestOptions(
            secret_key=settings.clerk_secret_key,
            jwt_key=settings.clerk_jwt_key,
            authorized_parties=settings.clerk_authorized_parties,
            accepts_token=["session_token"],
        ),
    )
    if not state.is_signed_in:
        raise HTTPException(
            status_code=401,
            detail=state.reason or "unauthorized",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return state
```

A few decisions baked into this dependency are worth explaining.

**`HTTPBearer(auto_error=False)`**, not `OAuth2PasswordBearer`. Clerk is the issuer, you're not running an OAuth server, and the tutorial pattern of `OAuth2PasswordBearer(tokenUrl="token")` doesn't apply. `HTTPBearer` gives you the Swagger "Authorize" button and a clean security scheme in the OpenAPI spec without pretending you expose a password flow. `auto_error=False` lets Clerk's SDK emit the specific rejection reason (`SESSION_TOKEN_MISSING`, `TOKEN_EXPIRED`, etc.) instead of a generic 403. Source: [FastAPI security first steps](https://fastapi.tiangolo.com/tutorial/security/first-steps/).

**Pass `Request` directly into `authenticate_request()`.** FastAPI's `Request` is a Starlette object with a `headers` mapping. Clerk's [`Requestish`](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/types.py) protocol is structurally satisfied — you don't wrap it, don't convert, don't `.dict()` it.

**Module-level `authenticate_request`**, not `clerk.authenticate_request(...)` on an instance. Both work. Module-level is explicit about what's happening, avoids needing a `with Clerk(...)` context manager, and matches the pattern the now-archived [`clerk/fastapi-example`](https://github.com/clerk/fastapi-example) used. If you prefer the instance method, construct a module-level `sdk = Clerk(bearer_auth=settings.clerk_secret_key)` and call `sdk.authenticate_request(request, AuthenticateRequestOptions(...))`. The instance method auto-pulls `secret_key` from `bearer_auth` if you don't pass it explicitly.

**Networkless with `jwt_key`.** The `jwt_key=settings.clerk_jwt_key` argument is the PEM public key. When present, the SDK verifies the RS256 signature locally. When absent, the SDK falls back to the networked path: fetch JWKS from `https://api.clerk.com/v1/jwks`, cache by `kid`, retry on mismatch for key rotation. Both work; networkless is faster and more resilient. See [verifytoken.py](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/verifytoken.py) for the exact behavior.

**Explicit `authorized_parties`.** The [Manual JWT verification guide](https://clerk.com/docs/guides/sessions/manual-jwt-verification.md) says: "Neglecting to validate `azp` can expose your application to CSRF attacks." The list is your allow-list of frontend origins. It's a single line of defense, so don't skip it.

**Explicit `accepts_token=["session_token"]`.** The Python SDK's `AuthenticateRequestOptions.accepts_token` [defaults to `['any']`](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/types.py) — meaning without this argument, the dependency will also accept API keys, M2M tokens, and OAuth access tokens on the same endpoint. Clerk's [`authenticateRequest` reference](https://clerk.com/docs/reference/backend/authenticate-request.md) documents the default as `'session_token'` (the Node SDK enforces that value). The Python SDK is permissive where the docs are restrictive, so pass `accepts_token` explicitly on every session-only endpoint. Machine-auth endpoints swap the list (shown in [Section 10f](#machine-to-machine-authentication)).

**Return `RequestState`, not `state.payload`.** The full state includes `is_signed_in` (and its alias `is_authenticated`), `status`, `reason`, `payload`, `token`, and `to_auth()`. Downstream dependencies (`require_permission`, `current_user`) want all of it.

### Protecting different types of endpoints

Three patterns you'll need: public, authenticated, and permission-gated.

Public endpoints need no dependency at all. Create `app/routers/public.py`:

```python
from fastapi import APIRouter

router = APIRouter()

@router.get("/health")
def health():
    return {"status": "ok"}

@router.get("/api/public/posts")
def public_posts():
    return {"posts": [{"id": 1, "title": "Hello, world"}]}
```

Authenticated endpoints inject `require_auth`. Create `app/routers/protected.py`:

```python
from typing import Annotated

from clerk_backend_api.security.types import RequestState
from fastapi import APIRouter, Depends

from app.auth import require_auth

router = APIRouter(prefix="/api", tags=["protected"])

@router.get("/me")
def me(state: Annotated[RequestState, Depends(require_auth)]):
    return {
        "user_id": state.payload["sub"],
        "session_id": state.payload.get("sid"),
    }
```

Admin-only endpoints stack a permission check on top. We'll build the `require_permission` factory in the next subsection. For now, the shape:

```python
@router.get("/admin/users")
def list_admin_users(
    _: Annotated[None, Depends(require_permission("org:admin:manage"))],
):
    return {"users": []}
```

A note on [roles](https://clerk.com/glossary.md#roles) and permissions that often trip readers up. The v2 session token carries the user's system role in the `o.rol` claim (without the `org:` prefix, e.g., `"admin"`), and the Python SDK enriches the payload with `payload["org_role"]` so you can read it directly — no Backend API call. **Custom permissions** are serialized compactly across three claims (`fea`, `o.per`, `o.fpm`) that the SDK decodes into the full `org:<feature>:<permission>` strings at `payload["org_permissions"]`. **System permissions** (like `org:sys_memberships:manage`) are _not_ serialized at all — if you need those server-side, create a custom permission and assign it to the system role in the Dashboard. Full details and worked examples are in the [RBAC section](#role-based-and-permission-based-access-control). Sources: [session tokens guide](https://clerk.com/docs/guides/sessions/session-tokens.md), [roles and permissions guide](https://clerk.com/docs/guides/organizations/control-access/roles-and-permissions.md).

### Accessing user context inside endpoints

Reading the user ID is a single claim lookup:

```python
user_id = state.payload["sub"]
```

That's enough for 90% of endpoints. The session token already has the user ID, organization ID, and custom permissions. You do not need to make a Backend API call to learn who the user is.

When you need profile data (name, email, metadata), call `sdk.users.get(user_id=user_id)`. Construct the SDK client once at module scope so you reuse its connection pool:

```python
from clerk_backend_api import Clerk
from functools import lru_cache

from app.config import get_settings

@lru_cache
def get_clerk() -> Clerk:
    return Clerk(bearer_auth=get_settings().clerk_secret_key)
```

Then in an endpoint:

```python
from typing import Annotated
from clerk_backend_api import Clerk
from clerk_backend_api.security.types import RequestState
from fastapi import APIRouter, Depends

from app.auth import require_auth

router = APIRouter(prefix="/api")

@router.get("/me/profile")
def profile(
    state: Annotated[RequestState, Depends(require_auth)],
    clerk: Annotated[Clerk, Depends(get_clerk)],
):
    user = clerk.users.get(user_id=state.payload["sub"])
    return {
        "id": user.id,
        "email": user.primary_email_address,
        "public_metadata": user.public_metadata,
    }
```

Because FastAPI caches dependency results per request (`use_cache=True` default), adding `state` to multiple sub-dependencies doesn't cost extra calls.

**Bridging auth to your own database.** The `state.payload["sub"]` value is the Clerk user ID (`user_xxxxxxxxxxxx`). Use it as a foreign key into your own tables. A minimal SQLAlchemy 2.0 async lookup:

```python
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models import User

async def load_user(session: AsyncSession, clerk_id: str) -> User | None:
    result = await session.execute(
        select(User).where(User.clerk_id == clerk_id)
    )
    return result.scalar_one_or_none()
```

The column name is `clerk_id`, matching Clerk's [syncing guide](https://clerk.com/docs/guides/development/webhooks/syncing.md). Not `clerk_user_id`, and not `external_id` — `external_id` is a distinct Clerk concept for importing third-party user IDs onto the Clerk user object. The `User` model itself is defined in Section 10d next to the webhook sync handler. SQLModel users can write the same thing with identical `Mapped` syntax.

### Role-based and permission-based access control

The two [RBAC](https://clerk.com/glossary.md#role-based-access-control-rbac) patterns worth knowing: custom permissions (in JWT, free) and system-role lookups (Backend API, slower).

**Custom permissions** follow the `org:<feature>:<permission>` dashboard key format (e.g., `org:invoices:create`, `org:reports:read`). That full string is what you check against in code — but it is not what's literally in the session token. v2 session tokens encode permissions [compactly across three claims](https://clerk.com/docs/guides/sessions/session-tokens.md):

1. `fea` — a top-level claim listing enabled features with scope prefixes, e.g. `"o:dashboard,o:teams"`.
2. `o.per` — a comma-separated list of bare permission names shared across features, e.g. `"manage,read"`.
3. `o.fpm` — a comma-separated list of integers, one per feature in `fea`. Each integer is a bitmask: bit `j` (right-to-left, 1-indexed) indicates whether the permission at index `j` of `o.per` applies to that feature.

Worked example from the [session-tokens guide](https://clerk.com/docs/guides/sessions/session-tokens.md): if a role has `dashboard:read`, `dashboard:manage`, `teams:read`, the claims are `fea="o:dashboard,o:teams"`, `o.per="manage,read"`, `o.fpm="3,2"`. Bit-decoding `3` = `11` = both `manage` and `read` for `dashboard`. Decoding `2` = `10` = only `read` for `teams`. The reconstructed permission list is `["org:dashboard:manage", "org:dashboard:read", "org:teams:read"]`.

The good news: the Python SDK does this decode for you. `authenticate_request()` calls an internal `_compute_org_permissions()` helper that reads `fea`, `o.per`, and `o.fpm`, reconstructs the full `org:<feature>:<permission>` strings, and writes the result as a plain list at `payload["org_permissions"]`. Membership testing is straightforward:

```python
from typing import Annotated
from clerk_backend_api.security.types import RequestState
from fastapi import Depends, HTTPException

from app.auth import require_auth

def require_permission(permission: str):
    def _check(state: Annotated[RequestState, Depends(require_auth)]) -> RequestState:
        org_permissions = state.payload.get("org_permissions") or []
        if permission not in org_permissions:
            raise HTTPException(
                status_code=403,
                detail=f"missing permission: {permission}",
            )
        return state
    return _check
```

Use it in a route:

```python
@router.post("/invoices")
def create_invoice(
    _: Annotated[RequestState, Depends(require_permission("org:invoices:create"))],
):
    return {"ok": True}
```

> Do not split `o.per` on commas and compare directly to `org:invoices:create`. `o.per` contains bare names like `manage,read` — the `org:<feature>:` prefix is reconstructed from `fea`/`o.fpm`, not stored in `o.per`. Using the reconstructed `payload["org_permissions"]` list is the correct way to check permissions server-side. If you're curious about the exact decode logic, see [`_compute_org_permissions` in `authenticaterequest.py`](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/authenticaterequest.py), which mirrors the JavaScript SDK's [`buildOrgPermissions`](https://github.com/clerk/javascript/blob/main/packages/shared/src/jwtPayloadParser.ts).

**System roles** live in the `o.rol` claim for the user's active organization, without the `org:` prefix (e.g., `"admin"`, `"member"`). The Python SDK copies that value to `payload["org_role"]` during `authenticate_request()` — see [`_process_payload` in `authenticaterequest.py`](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/authenticaterequest.py). So the role check is local, with no network call:

```python
def require_system_role(role: str):
    """Check role for the user's ACTIVE organization (the one in `o.rol`).

    Pass the role without the `org:` prefix (e.g., `"admin"`, `"member"`) to
    match what Clerk stores in the claim.
    """
    def _check(
        state: Annotated[RequestState, Depends(require_auth)],
    ) -> RequestState:
        if not state.payload.get("org_id"):
            raise HTTPException(status_code=403, detail="no organization context")
        if state.payload.get("org_role") != role:
            raise HTTPException(status_code=403, detail=f"missing role: {role}")
        return state
    return _check
```

Use it in a route:

```python
@router.delete("/api/org/members/{member_id}")
def remove_member(
    member_id: str,
    _: Annotated[RequestState, Depends(require_system_role("admin"))],
):
    ...
```

A caveat: `o.rol` only carries the role for the **active** organization — the one referenced by `o.id`. If the user belongs to multiple organizations and you need to check a specific one other than the active org, or enumerate every membership, fall back to the Backend API:

```python
memberships = clerk.users.get_organization_memberships(user_id=state.payload["sub"])
```

Prefer the local `payload["org_role"]` check whenever you're authorizing against the active org — it's faster and doesn't depend on the Backend API being reachable.

> **System permissions** (`org:sys_memberships:manage`, `org:sys_profile:manage`, etc.) are _not_ serialized into the token. If you need to authorize against a system permission on the server, create a custom permission in the Dashboard, assign it to the system role you want to check, and then look for it in `payload["org_permissions"]`. Source: [roles and permissions guide](https://clerk.com/docs/guides/organizations/control-access/roles-and-permissions.md) — "System Permissions aren't included in session claims."

Source: [roles and permissions](https://clerk.com/docs/guides/organizations/control-access/roles-and-permissions.md), [session token reference](https://clerk.com/docs/guides/sessions/session-tokens.md).

### Handling authentication errors gracefully

When `is_signed_in` is `False`, `state.reason` carries a machine-readable code. The [security/types.py](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/types.py) source has the full enum:

1. `SESSION_TOKEN_MISSING` — no token on the request
2. `TOKEN_EXPIRED` — expired JWT
3. `TOKEN_INVALID_SIGNATURE` — bad signature
4. `TOKEN_INVALID_AUTHORIZED_PARTIES` — `azp` not in your allow-list
5. `TOKEN_INVALID_ISSUER` — wrong Clerk instance
6. `TOKEN_TYPE_NOT_SUPPORTED` — got an M2M token on a session-only endpoint

Map them consistently. A custom exception handler for a uniform JSON shape:

```python
from fastapi import Request
from fastapi.responses import JSONResponse

from app.main import app

@app.exception_handler(HTTPException)
def http_exception_handler(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"error": "unauthorized" if exc.status_code == 401 else "forbidden",
                 "reason": exc.detail},
        headers=exc.headers or {},
    )
```

A few guardrails to keep in mind. Always include `WWW-Authenticate: Bearer` on 401s — it's [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750) compliance and some clients expect it. Never log the raw `Authorization` header or the token itself. Log the rejection reason, the request path, and (if known) the user ID. That's enough to debug without creating a [CWE-532](https://cwe.mitre.org/data/definitions/532.html) "Insertion of Sensitive Information into Log File" vulnerability.

### Testing your FastAPI endpoints

The sync `TestClient` still works and is the simplest default:

```python
import pytest
from fastapi.testclient import TestClient

from app.auth import require_auth
from app.main import app

def _fake_auth():
    class FakeState:
        is_signed_in = True
        payload = {"sub": "user_fake123", "sid": "sess_fake"}
        reason = None
    return FakeState()

@pytest.fixture(autouse=True)
def _override_auth():
    app.dependency_overrides[require_auth] = _fake_auth
    yield
    app.dependency_overrides = {}

def test_me_endpoint():
    client = TestClient(app)
    response = client.get("/api/me")
    assert response.status_code == 200
    assert response.json() == {"user_id": "user_fake123", "session_id": "sess_fake"}

def test_unauthenticated_returns_401():
    app.dependency_overrides = {}
    client = TestClient(app)
    response = client.get("/api/me")
    assert response.status_code == 401
```

Overriding `require_auth` at the dependency level (as above) is cleaner than patching `authenticate_request` globally — it leaves the rest of the chain (CORS, middleware, validation) real.

For async tests that need to `await` something (async DB sessions, an external API), switch to `httpx.AsyncClient` with `ASGITransport`:

```python
import pytest
from httpx import ASGITransport, AsyncClient

from app.main import app

@pytest.mark.asyncio
async def test_me_async():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        response = await ac.get("/api/me")
    assert response.status_code == 200
```

Add [`pytest-asyncio`](https://pypi.org/project/pytest-asyncio/) (1.3.0+ on PyPI, 2025-11-10) and put this in `pyproject.toml`:

```toml
[tool.pytest_asyncio]
asyncio_mode = "auto"
```

With `asyncio_mode = "auto"`, every `async def test_*` function is auto-marked — you don't have to sprinkle `@pytest.mark.asyncio` on each one. `@pytest.mark.anyio` is FastAPI's own preferred spelling (it can run the same test under both asyncio and trio) and is equivalent for an asyncio-only project.

One gotcha: `AsyncClient + ASGITransport` does _not_ trigger FastAPI's lifespan events (startup and shutdown). If your tests depend on startup hooks (database connection pools, etc.), wrap with [`asgi-lifespan`](https://pypi.org/project/asgi-lifespan/)'s `LifespanManager`:

```python
from asgi_lifespan import LifespanManager

@pytest.mark.asyncio
async def test_with_lifespan():
    async with LifespanManager(app):
        async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
            response = await ac.get("/api/me")
```

Don't over-mock. If you patch the entire auth chain, you'll miss real token parsing bugs. For integration tests against a real Clerk instance, sign in via a dev-instance frontend and grab a session token with `getToken()`. Sources: [FastAPI testing dependencies](https://fastapi.tiangolo.com/advanced/testing-dependencies/), [FastAPI async tests](https://fastapi.tiangolo.com/advanced/async-tests/), [pytest-asyncio concepts](https://pytest-asyncio.readthedocs.io/en/stable/concepts.html).

## Adding Clerk authentication to Flask

Parallel section to FastAPI. Flask is simpler, less opinionated, and the integration is a decorator instead of a dependency. If you're coming from FastAPI, the patterns map one-to-one; only the syntax changes.

### Project setup from scratch

```bash
uv init python-flask-auth
cd python-flask-auth
uv add flask clerk-backend-api python-dotenv
```

Minimal `app/__init__.py`:

```python
import os
from flask import Flask
from dotenv import load_dotenv

load_dotenv()


def env_csv(key: str, default: list[str] | None = None) -> list[str]:
    raw = os.environ.get(key)
    if not raw:
        return default or []
    return [p.strip() for p in raw.split(",") if p.strip()]


def create_app() -> Flask:
    app = Flask(__name__)
    app.config.update(
        CLERK_SECRET_KEY=os.environ["CLERK_SECRET_KEY"],
        CLERK_JWT_KEY=os.environ.get("CLERK_JWT_KEY"),
        CLERK_AUTHORIZED_PARTIES=env_csv("CLERK_AUTHORIZED_PARTIES"),
        CLERK_WEBHOOK_SIGNING_SECRET=os.environ.get("CLERK_WEBHOOK_SIGNING_SECRET"),
    )

    @app.get("/health")
    def health():
        return {"status": "ok"}

    from app.routes.protected import bp as protected_bp
    app.register_blueprint(protected_bp)

    return app
```

Run the dev server:

```bash
uv run flask --app app run --debug --port 5000
```

For production: `gunicorn "app:create_app()" -w 4 -b 0.0.0.0:8000`. The `-w $((2*$(nproc)+1))` formula from the Gunicorn docs is a reasonable default for I/O-bound Python apps. Source: [Flask testing docs](https://flask.palletsprojects.com/en/stable/testing/).

### The Flask auth decorator

Flask's canonical pattern for cross-cutting concerns is a decorator. Build it once, use it everywhere.

Create `app/auth.py`:

```python
from functools import wraps
from clerk_backend_api import AuthenticateRequestOptions, authenticate_request
from flask import abort, current_app, g, request


def clerk_required(view):
    @wraps(view)
    def wrapper(*args, **kwargs):
        state = authenticate_request(
            request,
            AuthenticateRequestOptions(
                secret_key=current_app.config["CLERK_SECRET_KEY"],
                jwt_key=current_app.config.get("CLERK_JWT_KEY"),
                authorized_parties=current_app.config["CLERK_AUTHORIZED_PARTIES"],
                accepts_token=["session_token"],
            ),
        )
        if not state.is_signed_in:
            abort(401, description=state.reason or "unauthorized")
        g.auth_state = state
        g.user_id = state.payload["sub"]
        return view(*args, **kwargs)
    return wrapper
```

A few Flask-specific decisions worth calling out.

**Flask's `request` satisfies `Requestish`.** Same as FastAPI, you pass it directly — no wrapping. Flask's `EnvironHeaders` is a case-insensitive mapping; Clerk's [security/types.py](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/security/types.py) just needs `headers: Mapping[str, str]`.

**`g` is Flask's per-request namespace.** It's thread-safe (backed by `contextvars`), scoped to a single request, and the idiomatic place to stash data that multiple handlers need. We put the full state on `g.auth_state` and the user ID on `g.user_id` as a shortcut.

**`functools.wraps`.** Without it, the decorated function loses its name and Flask's routing breaks (endpoints become `wrapper`, routes collide). Always wrap.

**`env_csv` in `create_app`.** Flask has no built-in type coercion, so we convert the `CLERK_AUTHORIZED_PARTIES` string to a list once at startup. The naive `os.environ.get("CLERK_AUTHORIZED_PARTIES", "").split(",")` returns `[""]` on an unset var — which doesn't actually fail the Clerk `azp` check (the empty list would), but it leaks an empty string into the comparison and makes debugging harder. Trim and filter.

### Alternative: `before_request` global middleware

When almost every route is protected, a decorator on every view feels noisy. Global middleware via `before_request` is cleaner.

```python
from flask import g, request, abort
from clerk_backend_api import AuthenticateRequestOptions, authenticate_request


PUBLIC_ENDPOINTS = {"static", "health", "clerk_webhook"}


def register_auth_middleware(app):
    @app.before_request
    def _authenticate():
        if request.endpoint in PUBLIC_ENDPOINTS:
            return None
        state = authenticate_request(
            request,
            AuthenticateRequestOptions(
                secret_key=app.config["CLERK_SECRET_KEY"],
                jwt_key=app.config.get("CLERK_JWT_KEY"),
                authorized_parties=app.config["CLERK_AUTHORIZED_PARTIES"],
                accepts_token=["session_token"],
            ),
        )
        if not state.is_signed_in:
            abort(401, description=state.reason or "unauthorized")
        g.auth_state = state
        g.user_id = state.payload["sub"]
```

Call `register_auth_middleware(app)` inside `create_app()`. The allow-list uses `request.endpoint` (the Flask route name, e.g. `health`) rather than `request.path` (`/health`) because endpoint names are more stable when you add prefixes or blueprints.

Per-route decorator vs. global middleware is a tradeoff. Prefer the decorator if most routes are public. Prefer global middleware if most routes are protected and you want auth-by-default. Don't mix both in the same app — it becomes hard to reason about which path ran which check.

### Protecting different types of endpoints

Create `app/routes/protected.py`:

```python
from flask import Blueprint, g, jsonify

from app.auth import clerk_required, require_permission

bp = Blueprint("protected", __name__, url_prefix="/api")


@bp.get("/public/posts")
def public_posts():
    return jsonify({"posts": [{"id": 1, "title": "Hello, world"}]})


@bp.get("/me")
@clerk_required
def me():
    return jsonify({"user_id": g.user_id})


@bp.get("/admin/users")
@clerk_required
@require_permission("org:admin:manage")
def list_admin_users():
    return jsonify({"users": []})
```

Decorator order matters. Flask applies decorators bottom-up, so the route registration is outermost (`@bp.get`), then `@clerk_required`, then `@require_permission`. Reading top-down: the route registers the view, auth runs next, permission runs last — which is what you want. If you flipped `@clerk_required` and `@require_permission`, the permission check would run against an uninitialized `g.auth_state` and crash.

### Accessing user context inside view functions

`g.user_id` is already set. For profile data, call `sdk.users.get()` via a cached helper:

```python
from functools import cache
from clerk_backend_api import Clerk
from flask import current_app, g


@cache
def _sdk() -> Clerk:
    return Clerk(bearer_auth=current_app.config["CLERK_SECRET_KEY"])


def current_user():
    if "_current_user" not in g:
        g._current_user = _sdk().users.get(user_id=g.user_id)
    return g._current_user
```

This gives you a `current_user()` API similar in feel to what Flask-Login developers are used to, but pointed at Clerk.

**Bridging to your database.** Once the decorator has verified the token, `g.user_id` is the Clerk user ID you join against. A SQLAlchemy 2.0 sync lookup (works with Flask-SQLAlchemy 3.x or plain SQLAlchemy):

```python
from sqlalchemy import select
from app.extensions import db
from app.models import User

@bp.get("/me/subscription")
@clerk_required
def my_subscription():
    user = db.session.scalar(select(User).where(User.clerk_id == g.user_id))
    if not user:
        return {"subscription": "free"}
    return {"subscription": user.subscription_tier}
```

If you're on Flask-SQLAlchemy 2.x, `User.query.filter_by(clerk_id=g.user_id).first()` works, but the 2.0 `select()` style matches what upstream SQLAlchemy will support going forward. Column name is `clerk_id`, per Clerk's [syncing guide](https://clerk.com/docs/guides/development/webhooks/syncing.md). The full `User` model is in Section 10d.

### RBAC and permission checks

Same pattern as FastAPI — read the decoded `payload["org_permissions"]` list that `authenticate_request()` populates from the v2 `fea` / `o.per` / `o.fpm` claims:

```python
from functools import wraps
from flask import g, abort


def require_permission(permission: str):
    def decorator(view):
        @wraps(view)
        def wrapper(*args, **kwargs):
            org_permissions = g.auth_state.payload.get("org_permissions") or []
            if permission not in org_permissions:
                abort(403, description=f"missing permission: {permission}")
            return view(*args, **kwargs)
        return wrapper
    return decorator
```

For system-role checks, read `payload["org_role"]` — same local check as FastAPI. The Python SDK populates it from the v2 `o.rol` claim during `authenticate_request()`, so no Backend API call is needed for the active organization:

```python
def require_system_role(role: str):
    """Pass `role` without the `org:` prefix (e.g., `"admin"`)."""
    def decorator(view):
        @wraps(view)
        def wrapper(*args, **kwargs):
            payload = g.auth_state.payload
            if not payload.get("org_id"):
                abort(403, description="no organization context")
            if payload.get("org_role") != role:
                abort(403, description=f"missing role: {role}")
            return view(*args, **kwargs)
        return wrapper
    return decorator
```

If you need to enumerate every organization the user belongs to (not just the active one), or check a role in a non-active organization, you still fall back to `_sdk().users.get_organization_memberships(user_id=g.user_id)` — that's the only case where a Backend API call is required.

### Error handling for APIs

Flask's default 401 renders HTML. For a JSON API, register a handler:

```python
from flask import jsonify
from werkzeug.exceptions import HTTPException


def register_error_handlers(app):
    @app.errorhandler(HTTPException)
    def _json_errors(exc: HTTPException):
        return jsonify({
            "error": "unauthorized" if exc.code == 401 else
                     "forbidden" if exc.code == 403 else
                     exc.name.lower().replace(" ", "_"),
            "reason": exc.description,
        }), exc.code
```

Register this inside `create_app()`. Same error shape as the FastAPI example, so clients can code against a consistent contract regardless of which Python framework your team landed on.

### Testing Flask endpoints

Flask's [`test_client()`](https://flask.palletsprojects.com/en/stable/testing/) is the idiomatic path. The key design decision: use **two fixtures**, one that exercises the real `clerk_required` decorator (for 401 tests) and one that injects a fake authenticated user (for happy-path tests). The common tempting shortcut — `monkeypatch.setattr("app.auth.clerk_required", ...)` — does _not_ work, because routes captured the original decorator reference at import time and pytest's monkeypatch can't retroactively rebind those references.

The cleanest pattern makes `clerk_required` test-aware via a config flag, and injects the fake user with `@app.before_request`. Update `app/auth.py` to honor the flag:

```python
from flask import current_app, g, request
from functools import wraps

def clerk_required(view):
    @wraps(view)
    def wrapper(*args, **kwargs):
        if current_app.config.get("TESTING_AUTH") and hasattr(g, "user_id"):
            return view(*args, **kwargs)  # trust before_request-injected g
        # ...real authenticate_request() path from Section 7b...
    return wrapper
```

Then in `tests/conftest.py`:

```python
import pytest
from flask import g

from app import create_app


@pytest.fixture
def app():
    """Real app with the real clerk_required decorator wired up."""
    app = create_app()
    app.config.update(TESTING=True)
    return app


@pytest.fixture
def client(app):
    """Anonymous client — no auth injected. Real decorator runs and returns 401."""
    with app.test_client() as c:
        yield c


@pytest.fixture
def authenticated_client(app):
    """Client with a fake authenticated user injected via before_request."""
    app.config["TESTING_AUTH"] = True

    @app.before_request
    def _inject_fake_auth():
        g.auth_state = type("S", (), {"payload": {"sub": "user_fake"}})()
        g.user_id = "user_fake"

    with app.test_client() as c:
        yield c
```

Now each test picks the fixture that matches the path it wants to exercise:

```python
def test_me_returns_user_id(authenticated_client):
    response = authenticated_client.get("/api/me")
    assert response.status_code == 200
    assert response.get_json() == {"user_id": "user_fake"}


def test_unauthenticated_returns_401(client):
    response = client.get("/api/me")
    assert response.status_code == 401
    assert response.get_json()["error"] == "unauthorized"
```

`client` runs the real decorator (no bypass), so the 401 assertion actually exercises `authenticate_request()`. `authenticated_client` sets `TESTING_AUTH=True` and populates `g` — the decorator short-circuits cleanly, and because the `app` fixture is function-scoped, the hook never leaks across tests.

For integration tests against a real Clerk dev instance, use a session token from a browser session: sign in via a local frontend, call `window.Clerk.session.getToken()` in the browser console, then use that token in `client.get('/api/me', headers={'Authorization': f'Bearer {token}'})`.

## Brief: using Clerk with Django / DRF

Clerk doesn't ship a first-party Django package in 2026. The [`clerk/django-example`](https://github.com/clerk/django-example) repository exists as a reference but is archived. Community packages (`clerk-django` 1.0.3, `django-clerk` 0.1.15) haven't seen updates since 2024 — don't build on them.

The idiomatic Django integration uses Django REST Framework's [`BaseAuthentication`](https://www.django-rest-framework.org/api-guide/authentication/) class. DRF's contract is that `authenticate()` returns a `(user, auth)` two-tuple, and the `user` object only has to expose `is_authenticated = True` to satisfy `IsAuthenticated` and related permissions. That lets us skip the Django user model entirely — Clerk owns the directory, and a lightweight `ClerkUser` stub is enough for request authorization.

```python
from dataclasses import dataclass

from clerk_backend_api import AuthenticateRequestOptions, authenticate_request
from django.conf import settings
from rest_framework.authentication import BaseAuthentication


@dataclass
class ClerkUser:
    """Minimal `request.user` for Clerk-authenticated requests.

    Clerk owns the user directory; this object exposes just enough for
    DRF's `IsAuthenticated` permission. If the app also maintains a
    local User row (via Clerk webhooks), look it up by `id` — the Clerk
    `sub` — in the view or a thin helper.
    """

    id: str
    payload: dict

    is_authenticated: bool = True
    is_anonymous: bool = False
    is_active: bool = True

    def __str__(self) -> str:
        return self.id


class ClerkAuthentication(BaseAuthentication):
    def authenticate(self, request):
        state = authenticate_request(
            request,
            AuthenticateRequestOptions(
                secret_key=settings.CLERK_SECRET_KEY,
                jwt_key=settings.CLERK_JWT_KEY,
                authorized_parties=settings.CLERK_AUTHORIZED_PARTIES,
                accepts_token=["session_token"],
            ),
        )
        if not state.is_signed_in:
            return None
        user = ClerkUser(id=state.payload["sub"], payload=state.payload)
        return (user, state)

    def authenticate_header(self, request):
        return "Bearer"
```

If you need to join request data against local rows (profiles, subscriptions, app-specific fields), sync users via Clerk webhooks and look up the local record by the Clerk ID exposed as `request.user.id`. See [Sync Clerk data to your application with webhooks](https://clerk.com/docs/guides/development/webhooks/syncing.md). Clerk's syncing guide explicitly recommends skipping a local user table when you don't need one: _"If you can access the necessary data directly from the Clerk session token, you can achieve strong consistency while avoiding the overhead of maintaining a separate user table."_

Register it in `settings.py`:

```python
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "yourapp.auth.ClerkAuthentication",
    ],
}
```

Permission checks on a `ViewSet`:

```python
from rest_framework.permissions import BasePermission


class HasClerkPermission(BasePermission):
    def __init__(self, permission: str):
        self.permission = permission

    def has_permission(self, request, view):
        state = request.auth  # the RequestState returned by authenticate()
        org_permissions = state.payload.get("org_permissions") or []
        return self.permission in org_permissions
```

For plain Django (not DRF), subclass `MiddlewareMixin` and populate `request.user` from the authenticated state inside `process_request`. Docs: [DRF authentication](https://www.django-rest-framework.org/api-guide/authentication/).

## Integrating with a React frontend

Python doesn't render sign-in UI. Something has to. The most common frontend pairing with a Python backend is React (or Next.js with React under the hood). Here's the handshake.

### Minimal React setup

```bash
npm install @clerk/clerk-react
```

Wrap your app:

```tsx
import { ClerkProvider, SignedIn, SignedOut, SignIn, UserButton } from '@clerk/clerk-react'

const publishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

export default function App() {
  return (
    <ClerkProvider publishableKey={publishableKey}>
      <SignedOut>
        <SignIn />
      </SignedOut>
      <SignedIn>
        <UserButton />
        <ProtectedPage />
      </SignedIn>
    </ClerkProvider>
  )
}
```

That's the whole frontend prerequisite. Full setup: [Clerk React quickstart](https://clerk.com/docs/react/getting-started/quickstart.md).

### Calling your Python API from React

The pattern that does the work:

```tsx
import { useAuth } from '@clerk/clerk-react'

function ProtectedPage() {
  const { getToken } = useAuth()

  async function fetchMe() {
    const token = await getToken()
    const res = await fetch('http://localhost:8000/api/me', {
      headers: { Authorization: `Bearer ${token}` },
    })
    return res.json()
  }

  return <button onClick={fetchMe}>Load profile</button>
}
```

`useAuth().getToken()` returns the short-lived (60-second) session JWT. The SDK auto-refreshes roughly every 50 seconds, so a long-lived page stays authenticated as long as the Clerk session is valid. Force a fresh token with `getToken({ skipCache: true })` if you're debugging expiry behavior.

### CORS on the Python side

If your frontend and Python backend live on different origins (`localhost:3000` and `localhost:8000` during development, or `app.example.com` and `api.example.com` in production), [CORS](https://fastapi.tiangolo.com/tutorial/cors/) has to allow the request.

FastAPI:

```python
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "https://yourapp.com"],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)
```

Flask:

```python
from flask_cors import CORS

CORS(
    app,
    resources={r"/api/*": {"origins": ["http://localhost:3000", "https://yourapp.com"]}},
    supports_credentials=True,
)
```

The gotcha in both frameworks: if you set `allow_credentials=True` (FastAPI) or `supports_credentials=True` (Flask), you cannot use `"*"` for origins. The browser rejects the response. List the origins explicitly, or don't use credentials. For bearer-token auth (`Authorization: Bearer ...`), you technically don't need credentials mode at all — that's only for cookies.

Same origins you list in `CORS_ORIGINS` should also appear in `CLERK_AUTHORIZED_PARTIES`. The two checks serve different layers (CORS protects the browser; `azp` protects the token), but they have to be consistent or requests fail.

## Advanced Clerk SDK features for Python backends

Once the basic auth loop works, these are the SDK corners most teams end up in.

### User metadata access

Clerk users have three metadata buckets:

1. `public_metadata` — readable from frontend and backend. Use for feature flags, subscription tiers, anything safe to ship to the browser.
2. `unsafe_metadata` — writable from the frontend SDK. Use for user-controlled preferences.
3. `private_metadata` — backend-only. Use for sensitive flags, internal IDs, things you don't want the user to see or change.

Reading and updating:

```python
clerk = Clerk(bearer_auth=os.environ["CLERK_SECRET_KEY"])

user = clerk.users.get(user_id="user_xxx")
print(user.public_metadata)  # {"subscription_tier": "pro"}

clerk.users.update(
    user_id="user_xxx",
    public_metadata={"subscription_tier": "enterprise"},
)
```

Common use case: Stripe webhook handler updates `public_metadata.subscription_tier` after a successful checkout. Frontend reads it from `useUser().user.publicMetadata` and gates premium features accordingly. No separate database needed for this data.

### Session management

List active sessions for a user, revoke one, or revoke all. "Sign out everywhere" is a three-line operation:

```python
sessions = clerk.sessions.list(user_id="user_xxx")
for session in sessions.data:
    if session.status == "active":
        clerk.sessions.revoke(session_id=session.id)
```

Revoking a session invalidates the refresh token; the user's browser will fail to refresh and be signed out on the next page load. The async equivalent uses `clerk.sessions.list_async(...)` on the same `Clerk` instance.

### Organizations and team features

Clerk [organizations](https://clerk.com/glossary.md#organizations) give you B2B [multi-tenancy](https://clerk.com/glossary/multi-tenancy.md) without rolling your own. The Backend API mirrors the frontend SDK:

```python
# Create an org
org = clerk.organizations.create(name="Acme Inc", slug="acme")

# List a user's orgs
memberships = clerk.users.get_organization_memberships(user_id="user_xxx")

# Invite a member
clerk.organization_invitations.create(
    organization_id=org.id,
    email_address="teammate@example.com",
    role="org:admin",
)
```

Roles and permissions management via Backend API shipped on [2025-11-24](https://clerk.com/changelog/2025-11-24-organization-roles-and-permission-bapi-management.md). You can now create custom roles and permissions programmatically (e.g., during org provisioning) instead of only via the Dashboard:

```python
role = clerk.organization_roles.create(
    instance_id="ins_xxx",  # your Clerk instance
    name="Analyst",
    key="org:analyst",
    description="Read-only access to reports",
)
```

### Webhook handling in Python

[Webhooks](https://clerk.com/glossary.md#webhook) are how Clerk tells your Python backend about user events — `user.created`, `user.updated`, `user.deleted`, organization events, role changes. Sync them into your database, trigger emails, update analytics, whatever.

Clerk delivers webhooks via [Svix](https://www.svix.com/). You verify the signature with Svix's Python SDK.

```bash
uv add svix
```

FastAPI handler:

```python
from fastapi import APIRouter, HTTPException, Request
from svix.webhooks import Webhook, WebhookVerificationError

from app.config import get_settings

router = APIRouter()

@router.post("/webhooks/clerk")
async def clerk_webhook(request: Request):
    body = await request.body()
    headers = dict(request.headers)
    settings = get_settings()
    try:
        event = Webhook(settings.clerk_webhook_signing_secret).verify(body, headers)
    except WebhookVerificationError:
        raise HTTPException(status_code=400, detail="invalid signature")
    event_type = event["type"]
    data = event["data"]
    if event_type == "user.created":
        await handle_user_created(data)
    elif event_type == "user.deleted":
        await handle_user_deleted(data["id"])
    return {"ok": True}
```

Flask handler — same shape, different framework primitives:

```python
from flask import Blueprint, abort, current_app, request
from svix.webhooks import Webhook, WebhookVerificationError

bp = Blueprint("webhooks", __name__)


@bp.post("/webhooks/clerk")
def clerk_webhook():
    body = request.get_data()
    headers = dict(request.headers)
    try:
        event = Webhook(
            current_app.config["CLERK_WEBHOOK_SIGNING_SECRET"]
        ).verify(body, headers)
    except WebhookVerificationError:
        abort(400, description="invalid signature")
    if event["type"] == "user.created":
        handle_user_created(event["data"])
    return {"ok": True}
```

A few details to get right the first time.

**Use the raw body, not parsed JSON.** Signature verification is an HMAC over the raw bytes. If FastAPI or Flask has already parsed the JSON, the HMAC won't match. `await request.body()` (FastAPI) and `request.get_data()` (Flask) both return raw bytes.

**Required headers.** Svix's `Webhook.verify()` needs `svix-id`, `svix-timestamp`, and `svix-signature`. Clerk also sends the standardized `webhook-id`, `webhook-timestamp`, `webhook-signature` aliases; Svix accepts either. All three headers are always present — they're never optional.

**Casing is handled.** The Svix Python SDK lowercases headers on entry (see [svix/webhooks.py](https://github.com/svix/svix-webhooks/blob/main/python/svix/webhooks.py) line 15). FastAPI's `Request.headers` yields lowercase keys already; Flask's `EnvironHeaders` yields HTTP-canonical casing (`Svix-Id`) that Svix lowercases immediately. Either way, `dict(request.headers)` is safe to pass in.

**Reverse-proxy gotcha.** If your webhook endpoint sits behind Nginx, Cloudflare, or an API gateway, make sure the `svix-*` (or `webhook-*`) headers pass through untouched. Some WAFs strip unrecognized headers. If `Webhook.verify()` throws `WebhookHeadersError`, log the header keys (not values) to confirm the proxy isn't dropping them.

**Replay prevention.** Svix ships with a [built-in 5-minute timestamp window](https://www.svix.com/resources/webhook-best-practices/security/). Requests older than that fail verification automatically.

**Idempotency.** Persist processed `svix-id` values for about 24 hours; reject duplicates. Svix guarantees at-least-once delivery, so you will see the same event twice occasionally.

**The webhook endpoint must bypass auth.** Don't put `@clerk_required` or global auth middleware in front of it. Svix signs with a different secret than session tokens; `authenticate_request()` on the same request would reject it.

**Store Clerk's `id` as a foreign key, not a primary key.** Keep your own integer primary keys; make `clerk_id` a unique, indexed, non-null string column. This is what Clerk's [syncing guide](https://clerk.com/docs/guides/development/webhooks/syncing.md) recommends.

A minimal SQLAlchemy 2.0 `User` model to back the webhook handler:

```python
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    clerk_id: Mapped[str] = mapped_column(String(255), unique=True, index=True)
    email: Mapped[str | None] = mapped_column(String(320))
    subscription_tier: Mapped[str] = mapped_column(String(32), default="free")
```

About `String(255)`. Clerk user IDs are currently 32 characters (`user_` + 27 word chars per the [OpenAPI spec](https://github.com/clerk/openapi-specs) pattern `^user_\w{27}$`), but the schema component for `User.id` is plain `type: string` with no `maxLength` or `pattern` constraint. The Python SDK types it as plain `str`. Clerk's own public-API fields that carry user IDs use `maxLength: 255`. PostgreSQL stores `VARCHAR(n)` and `VARCHAR(255)` identically on disk. Tightening to `String(32)` saves nothing and creates a silent-failure risk if Clerk ever lengthens the ID format. Keep `String(255)`.

The `user.created` handler:

```python
async def handle_user_created(data: dict):
    clerk_id = data["id"]
    email = None
    for address in data.get("email_addresses") or []:
        if address["id"] == data.get("primary_email_address_id"):
            email = address["email_address"]
            break
    async with async_session() as session:
        user = User(clerk_id=clerk_id, email=email)
        session.add(user)
        await session.commit()
```

The `user.deleted` handler is symmetric:

```python
async def handle_user_deleted(clerk_id: str):
    async with async_session() as session:
        user = await session.scalar(
            select(User).where(User.clerk_id == clerk_id)
        )
        if user:
            await session.delete(user)
            await session.commit()
```

Source: [Svix receiving webhooks with FastAPI](https://www.svix.com/guides/receiving/receive-webhooks-with-python-fastapi/), [Svix receiving with Flask](https://www.svix.com/guides/receiving/receive-webhooks-with-python-flask/), [Svix verification internals](https://docs.svix.com/receiving/verifying-payloads/how).

### User impersonation for support

Support workflows often need "sign in as this user to see what they see." Clerk calls this feature **actor tokens**. The Python SDK [exposes it as `clerk.actor_tokens.create(...)`](https://raw.githubusercontent.com/clerk/clerk-sdk-python/main/src/clerk_backend_api/actortokens.py), which hits `POST /v1/actor_tokens` on the Backend API. The request body takes two mandatory fields: the user being impersonated (`user_id`) and the admin doing the impersonating (`actor.sub`). The admin is embedded in the issued session's `act` claim so the audit trail shows who did what.

```python
actor_token = clerk.actor_tokens.create(request={
    "user_id": "user_target_xxx",             # user being impersonated
    "actor": {"sub": "user_admin_xxx"},        # admin doing the impersonating (ends up in `act`)
    "expires_in_seconds": 3600,                # optional, default 1 hour
    "session_max_duration_in_seconds": 1800,   # optional, default 30 minutes
})

# Share the one-time sign-in URL with the support agent.
# Visiting it signs them in as the target user; the resulting session
# carries `act.sub = "user_admin_xxx"` so your audit logs track it back.
print(actor_token.url)
print(actor_token.token)  # raw ticket value if you want to build the URL yourself
```

Revoke before expiry with `clerk.actor_tokens.revoke(actor_token_id=actor_token.id)`.

Do **not** confuse `actor_tokens` with `sign_in_tokens` — the latter is a one-time sign-in link for a normal user (no `act` claim, no audit trail), not an impersonation primitive. Full guide: [user impersonation](https://clerk.com/docs/guides/users/impersonation.md).

### Machine-to-machine authentication

When a service calls another service (not on behalf of a user), you want M2M tokens. Clerk supports two formats:

1. **Opaque** (`m2m_xxx`) — network verification, revocable. Best when you want to invalidate a token immediately.
2. **JWT** (`mt_xxx`) — networkless verification, not revocable until expiry. Best for high-throughput service-to-service calls. Shipped [2026-02-24](https://clerk.com/changelog/2026-02-24-m2m-jwt-tokens.md).

Restrict an endpoint to M2M traffic only:

```python
state = authenticate_request(
    request,
    AuthenticateRequestOptions(
        secret_key=settings.clerk_secret_key,
        accepts_token=["m2m_token"],
    ),
)
```

API Keys (user-scoped programmatic access, prefix `ak_`) went [GA on 2026-04-17](https://clerk.com/changelog/2026-04-17-api-keys-ga.md). If you want both session tokens and API keys to work on the same endpoint, pass `accepts_token=["session_token", "api_key"]`. Full guide: [machine auth overview](https://clerk.com/docs/guides/development/machine-auth/overview.md).

## Production deployment considerations

Everything below is the stuff that breaks on the first 2 a.m. page if you skip it.

### Secret management

Never commit `.env`. `.gitignore` it and commit an `.env.example` with the variable names but no values.

Rotation plan for `CLERK_SECRET_KEY`: create a new secret key in the Dashboard, deploy it to production, wait one deploy cycle, delete the old one. Clerk accepts both during the window. Rotate annually or on suspected compromise. Keep `CLERK_JWT_KEY` (public) separate from `CLERK_SECRET_KEY` (private) in your secret store; the JWT key can live in plain environment config, but the secret key should be in a proper secret manager.

Options: AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Doppler, Infisical. Railway, Render, Fly.io, and Vercel all have encrypted-at-rest environment variable storage that's sufficient for small teams. Pick whichever fits your platform; the pattern (inject at boot, never log) is the same.

### CORS and authorized parties

Repeat of the gotcha from Section 9: `allow_origins=["*"]` + `allow_credentials=True` breaks every browser. List origins explicitly.

`authorized_parties` on `AuthenticateRequestOptions` is your CSRF backstop. Even if CORS is misconfigured, `azp` validation rejects tokens issued for origins you didn't list. It's a defense-in-depth check, not a substitute for CORS.

Subdomain strategies:

1. **Same-apex** (`app.example.com` + `api.example.com`) — same-site cookies work if you set the cookie domain to `.example.com`. [SameSite=Lax](https://clerk.com/glossary.md#samesite-cookie) is fine for most flows.
2. **Cross-origin** (`yourapp.com` + `api.someothercompany.com`) — you're in full cross-origin territory. Use `Authorization: Bearer ...`, don't bother with cookies, and be strict about CORS origins.

### Performance and caching

Networkless verification with `jwt_key` is one PEM decode per process, cached in memory. Each subsequent request is a local RS256 signature check — no network round-trip to Clerk.

Networked verification fetches JWKS from `https://api.clerk.com/v1/jwks` once per process per `kid`. Cached in memory. Re-fetches on signature mismatch (key rotation). The first request after startup pays a one-time network round-trip; every request after that is a local signature check against the cached key.

Don't call `sdk.users.get()` on every request. The session token already has `sub`, `sid`, and the org claims. Only fetch the full user when you need metadata you can't get from the token.

Reuse a single `Clerk()` instance at module scope. The SDK is [httpx](https://www.python-httpx.org/)-based, and reusing the underlying client means you benefit from HTTP connection pooling. The [httpx clients guide](https://www.python-httpx.org/advanced/clients/) documents this trade-off directly: without a long-lived `Client`, httpx has to "establish a new connection _for every single request_," while a reused client brings "reduced latency across requests (no handshaking)." Creating a new `Clerk()` per request is the pattern to avoid.

### Observability and logging

What to log per authenticated request: timestamp, `user_id`, `session_id`, request ID, endpoint, outcome (allowed / rejected), latency, rejection reason if any. That's enough to debug almost anything.

What to never log: the full token, the raw `Authorization` header, password hashes, PII like email addresses unless you have a reason.

Structured logging with [`structlog`](https://www.structlog.org/) (preferred) or [`loguru`](https://github.com/Delgan/loguru) (popular but set `diagnose=False` in production — it can leak secrets into tracebacks).

OpenTelemetry instrumentation for FastAPI and Flask: `opentelemetry-instrumentation-fastapi`, `opentelemetry-instrumentation-flask`, and `opentelemetry-instrumentation-httpx` (to trace outbound Clerk Backend API calls). Set `OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=authorization,cookie,svix-signature` so the tracing layer doesn't exfiltrate credentials to your APM.

### Deployment platforms

Short rundowns of where Python APIs actually run in 2026.

**Railway** — `railway.json` or `railway.toml` for config; `startCommand` for the worker command. Railway's [Railpack builder defaults to Python 3.13](https://railpack.com/languages/python); pin a different version with `.python-version`.

**Render** — `uvicorn app.main:app --host 0.0.0.0 --port $PORT` for FastAPI or `gunicorn "app:create_app()" -b 0.0.0.0:$PORT` for Flask. [Free tier sleeps after 15 minutes idle](https://render.com/docs/free); the [Starter plan is $7/month](https://render.com/pricing) for always-on.

**Fly.io** — `fly secrets set CLERK_SECRET_KEY=...` writes to an encrypted vault; the API servers [can only encrypt, not decrypt, stored secret values](https://fly.io/docs/apps/secrets/). Python docs at [fly.io/docs/python](https://fly.io/docs/python/).

**Heroku** — `Procfile`: `web: gunicorn -k uvicorn_worker.UvicornWorker app.main:app`. Newly created Python apps [default to the latest patch of Python 3.14](https://devcenter.heroku.com/articles/python-support); pin explicitly via `.python-version`.

**Google Cloud Run** — Dockerfile with `gunicorn` on `$PORT`; `--set-env-vars` or pull from Secret Manager with `roles/secretmanager.secretAccessor`. The [free tier includes 180,000 vCPU-seconds and 2 million requests per month](https://cloud.google.com/run/pricing) under request-based billing (240,000 vCPU-seconds under instance-based billing).

**AWS Lambda via Mangum** — `Mangum(app, lifespan="off")`. Initialize `Clerk()` and `httpx.Client` at module level so warm invocations reuse them. [Lambda SnapStart](https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html) is available for Python 3.12 and later to reduce cold-start latency. [Mangum on PyPI](https://pypi.org/project/mangum/).

**Vercel Python Functions** — [Supports Python 3.12 (default), 3.13, and 3.14](https://vercel.com/docs/functions/runtimes/python). Auto-detects FastAPI and Flask from `requirements.txt`. Good fit when your frontend is already on Vercel.

Cold-start concerns matter on serverless (Lambda, Cloud Run, Vercel). Networkless `jwt_key` mode eliminates the JWKS warmup cost — one less thing to worry about on the first request after a scale-up.

### Frontend-backend communication in production

Same-origin deployment: use [Next.js 16's `proxy.ts`](https://nextjs.org/blog/next-16) to proxy `/api/*` internally to your Python service via `rewrites`. `proxy.ts` replaces the deprecated `middleware.ts` as the top-level file in Next.js 16 (released 2025-10-21). Clerk's import path is unchanged — the function is still `clerkMiddleware()` from `@clerk/nextjs/server`; only the file name changed. Next.js 15 and earlier projects keep the file as `middleware.ts`.

Cross-origin deployment (SPA at `yourapp.com`, Python API at `api.yourapp.com`): no file-rename concern. Rely on `authorized_parties` on the Python side and an explicit `CORSMiddleware` allow-list.

Cookie `SameSite=Lax` for same-origin; bearer tokens for cross-origin. Don't try to make cookies work across third-party domains — browsers are actively removing third-party cookie support and you'll spend weeks debugging.

### Clerk production instance checklist

- [ ] Switch to `pk_live_` / `sk_live_` in production env vars.
- [ ] Configure DNS CNAMEs per the Dashboard's instructions (your `clerk.yourapp.com` subdomain has to exist).
- [ ] Set up your own OAuth credentials with each provider (Google, GitHub, etc.). Clerk's shared dev OAuth apps don't work in production.
- [ ] Verify your `authorized_parties` list matches your real production origins.
- [ ] Turn off dev-only sign-in methods if you don't want them (e.g., test email codes).

Canonical checklist: [deploy to production](https://clerk.com/docs/guides/development/deployment/production.md).

## Clerk vs. other Python authentication options

The options at a glance. Pricing and free-tier numbers are verified against vendor pricing pages in April 2026; capability rows cite a primary vendor source.

| Capability                                                                          | Clerk                                                                                                                       | Auth0                                                                                                                         | Supabase Auth                                                                                                                              | Firebase Auth                                                                                                                 | AWS Cognito                                                                                                                                                               | DIY (PyJWT + pwdlib)                                                                           |
| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| First-party Python SDK                                                              | `clerk-backend-api` 5.0.6 ([PyPI](https://pypi.org/project/clerk-backend-api/))                                             | `auth0-api-python` / `auth0-fastapi-api` ([Auth0 Python Quickstart](https://auth0.com/docs/quickstart/backend/python))        | Python server SDK via `supabase-py` ([docs](https://supabase.com/docs/reference/python/introduction))                                      | `firebase-admin` ([PyPI](https://pypi.org/project/firebase-admin/))                                                           | `boto3` (AWS SDK, generic)                                                                                                                                                | n/a                                                                                            |
| [Passkeys](https://clerk.com/glossary.md#passkeys) included in lowest paid tier     | Yes — Pro ([pricing](https://clerk.com/pricing))                                                                            | Yes — Essentials ([pricing](https://auth0.com/pricing))                                                                       | Not offered natively — requires external WebAuthn implementation ([auth docs](https://supabase.com/docs/guides/auth))                      | Not offered                                                                                                                   | Yes — Essentials ([pricing](https://aws.amazon.com/cognito/pricing/))                                                                                                     | DIY                                                                                            |
| MFA (TOTP) on free tier                                                             | Pro and above ([pricing](https://clerk.com/pricing))                                                                        | Yes ([pricing](https://auth0.com/pricing))                                                                                    | Yes ([MFA docs](https://supabase.com/docs/guides/auth/auth-mfa))                                                                           | Blaze (pay-as-you-go) plan only ([pricing](https://firebase.google.com/pricing))                                              | Essentials and above ([pricing](https://aws.amazon.com/cognito/pricing/))                                                                                                 | DIY                                                                                            |
| Native [organizations](https://clerk.com/glossary.md#organizations) / B2B primitive | Yes — first-class ([organizations docs](https://clerk.com/docs/guides/organizations/overview.md))                           | Yes — first-class ([Organizations](https://auth0.com/docs/manage-users/organizations))                                        | Not a first-class primitive — modeled via Postgres tables and [RLS](https://supabase.com/docs/guides/database/postgres/row-level-security) | Not offered                                                                                                                   | Groups (flat, not hierarchical) ([Cognito user groups](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-user-groups.html))                    | DIY                                                                                            |
| Webhooks for user events                                                            | Yes — via [Svix](https://www.svix.com/) ([webhook docs](https://clerk.com/docs/guides/development/webhooks/overview.md))    | Yes — Log Streams ([docs](https://auth0.com/docs/customize/log-streams))                                                      | Yes — auth hooks ([docs](https://supabase.com/docs/guides/auth/auth-hooks))                                                                | Cloud Functions [blocking triggers](https://firebase.google.com/docs/auth/extend-with-blocking-functions) (not HTTP webhooks) | Lambda Triggers ([docs](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html)) (not HTTP webhooks) | DIY                                                                                            |
| Local JWT verification with public key (no per-request network call)                | Yes — `jwt_key=` parameter ([manual verification guide](https://clerk.com/docs/guides/sessions/manual-jwt-verification.md)) | Yes — JWKS fetched and cached ([token validation](https://auth0.com/docs/secure/tokens/access-tokens/validate-access-tokens)) | Yes — JWKS / HS256 ([docs](https://supabase.com/docs/guides/auth/jwt-fields))                                                              | Yes — via `firebase-admin` [`verify_id_token`](https://firebase.google.com/docs/auth/admin/verify-id-tokens)                  | Yes — JWKS ([docs](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html))                                | DIY                                                                                            |
| Free-tier unit and allowance                                                        | 50,000 MRUs ([pricing](https://clerk.com/pricing))                                                                          | 7,500 MAUs ([pricing](https://auth0.com/pricing))                                                                             | 50,000 MAUs ([pricing](https://supabase.com/pricing))                                                                                      | 50,000 MAUs Spark plan ([pricing](https://firebase.google.com/pricing))                                                       | 10,000 MAUs Lite ([pricing](https://aws.amazon.com/cognito/pricing/))                                                                                                     | Free                                                                                           |
| Starting paid tier                                                                  | $20/mo annual ($25 monthly) Pro ([pricing](https://clerk.com/pricing))                                                      | $35/mo Essentials ([pricing](https://auth0.com/pricing))                                                                      | $25/mo Pro ([pricing](https://supabase.com/pricing))                                                                                       | pay-as-you-go Blaze ([pricing](https://firebase.google.com/pricing))                                                          | $0.015/MAU Essentials ([pricing](https://aws.amazon.com/cognito/pricing/))                                                                                                | $50K–$200K/yr SOC 2 ([WorkOS guide](https://workos.com/blog/python-authentication-guide-2026)) |
| SOC 2 report access tier                                                            | Business ([pricing](https://clerk.com/pricing))                                                                             | "Compliance Certifications" included all tiers ([pricing](https://auth0.com/pricing))                                         | Team and Enterprise ([Supabase security](https://supabase.com/security))                                                                   | Available under Google Cloud ([SOC 2](https://cloud.google.com/security/compliance/soc-2))                                    | Available under AWS ([AWS Compliance](https://aws.amazon.com/compliance/soc-faqs/))                                                                                       | DIY                                                                                            |

Numbers sourced from each vendor's pricing and compliance page (links in table), the [Clerk "new plans, more value" changelog](https://clerk.com/changelog/2026-02-05-new-plans-more-value.md) (2026-02-05 repricing), and [WorkOS's 2026 guide](https://workos.com/blog/python-authentication-guide-2026) for the DIY cost range.

Clerk's tier detail, since numbers move:

1. **Hobby** (free) — 50,000 MRUs. Basic RBAC (20-member org cap), 5 impersonations/month, 3 dashboard seats, 7-day fixed sessions. Does not include MFA, passkeys, or Enterprise SSO.
2. **Pro** — $20/month billed annually ($25 monthly). Adds MFA, passkeys, custom email templates, custom session duration, SMS codes, satellite domains ($10/mo each), remove Clerk branding, and 1 Enterprise SSO connection included ($75/mo per additional connection).
3. **Business** — $250/month billed annually ($300 monthly). Adds SOC 2 Report access, HIPAA artifact access, 10 dashboard seats (additional $20/mo each), enhanced dashboard roles, and priority support.
4. **Enterprise** — custom pricing, annual only. Adds **HIPAA compliance available with BAA**, 99.99% uptime SLA, premium support, dedicated onboarding, custom Slack channel, annual committed-use discounts, and Enterprise SSO for the workspace.

> The **Business** plan includes access to the SOC 2 report and HIPAA artifacts (compliance documentation). **Signing a BAA for HIPAA-covered production workloads requires the Enterprise plan**, per Clerk's pricing page. Verify with Clerk's [pricing page](https://clerk.com/pricing) and the [2026-02-05 "new plans, more value" changelog](https://clerk.com/changelog/2026-02-05-new-plans-more-value.md) before making a compliance decision — these tiers shift.

### When to pick Clerk

1. You want a first-party Python SDK with predictable release cadence.
2. You need strong B2B / organizations support, including programmatic role and permission management.
3. You already use React, Next.js, or Expo — Clerk's frontend components match the backend one-to-one.
4. You want [passkeys](https://clerk.com/glossary.md#passkeys), MFA, and social OAuth without building them.
5. You need SOC 2 Report access on the Business tier, or HIPAA BAA availability on Enterprise, for compliance reasons.

### When another option might fit better

**Supabase** if your whole stack is Supabase — Postgres plus realtime plus storage plus auth, all consolidated. The client-SDK focus on auth means you'll write more Python verification code manually, but you get a tight DB integration.

**Firebase** if you're deep in Google Cloud — Firestore, Cloud Functions triggers, App Check integration. The organizations gap matters if your app is B2B.

**Auth0** if you need extensive enterprise SSO beyond what Clerk offers in Pro. Auth0's Enterprise tier has more granular SAML/OIDC controls. Pricing starts at $35/month (7,500 MAU free) and scales up; the old "$1,600/mo at 10K MAU" number you might see in older comparisons was a briefly-available 2024 promotion that has rolled back.

**Cognito** if you're AWS-native with no other preference. You'll write more Python yourself (no dedicated FastAPI/Flask helper), but Lambda Triggers compose well with the rest of your stack.

**DIY** only if authentication is literally your product (you're building an IDP), or for learning. The time and compliance cost is hard to justify otherwise.

None of these are bad choices. Clerk is the default recommendation for a modern Python API paired with a modern frontend; the others fit legitimate niches.

## FAQ

## FAQ

### Does Clerk have an official Python SDK?

Yes. [`clerk-backend-api`](https://pypi.org/project/clerk-backend-api/) is the official SDK and supports Python 3.10 and later. It launched in beta on [2024-10-08](https://clerk.com/changelog/2024-10-08-python-backend-sdk-beta.md) and has shipped regular releases since, reaching version 5.0.6 in March 2026 per its [PyPI release history](https://pypi.org/project/clerk-backend-api/).

### What Python version does clerk-backend-api require?

Python 3.10 or higher. The `pyproject.toml` declares `requires-python = ">=3.10"`. If you have to stay on 3.8 or 3.9 for some reason, pin `clerk-backend-api<3` (pre-module-level `authenticate_request` API) as an escape hatch, but upgrading Python is the better path.

### Can Python handle Clerk sign-up and sign-in directly?

No. Sign-up and sign-in flows (including OAuth redirects, passkey WebAuthn prompts, and MFA challenges) happen in the browser or mobile client. The Python SDK verifies the signed session token that the frontend sends; it does not render sign-in forms or prompt the user for a password.

### Can I use Clerk with FastAPI, Flask, and Django?

Yes. The SDK is framework-agnostic — [`authenticate_request()`](https://clerk.com/docs/reference/backend/authenticate-request.md) accepts any object with a `headers` mapping, which FastAPI, Flask, Django, Starlette, and Sanic request objects all satisfy. This guide covers FastAPI and Flask in depth and Django via DRF `BaseAuthentication`.

### Do I need to call Clerk's API on every request to verify a token?

No. Session tokens are RS256 JWTs that verify locally with the public key. Pass `jwt_key=CLERK_JWT_KEY` into `AuthenticateRequestOptions` and every verification is an in-memory signature check — no network round-trip to Clerk. The networked fallback fetches JWKS from `https://api.clerk.com/v1/jwks` once per process per `kid` and caches it in memory.

### Where does Clerk look for the session token — cookie or header?

Both. `authenticate_request()` checks `Authorization: Bearer <token>` first, then the `__session` cookie as a fallback. Same-origin setups typically use the cookie; cross-origin SPAs and mobile clients use the header. The SDK handles either transparently.

### How do I get the user ID inside a FastAPI or Flask route?

Read `state.payload["sub"]` from the `RequestState` returned by `authenticate_request()`. Check signed-in status with `state.is_signed_in` (or the equivalent alias `state.is_authenticated`, or directly `state.status == AuthStatus.SIGNED_IN`). In FastAPI, inject `RequestState` via `Annotated[RequestState, Depends(require_auth)]`. In Flask, the `@clerk_required` decorator in this guide stashes `payload["sub"]` on `g.user_id`. No extra API call is needed to learn who the user is.

### How do I check roles or permissions in Python?

Use `state.payload["org_permissions"]`, which the SDK reconstructs from the compact v2 claims (`fea`, `o.per`, `o.fpm`) during `authenticate_request()`. It contains full `org:<feature>:<permission>` strings ready for membership tests. Do not split `o.per` directly — that claim holds bare names like `manage,read`. Use the `require_permission("org:invoices:create")` factories in Sections 6f and 7f of this guide.

### How do I check a system role like org:admin in Python?

Read `state.payload["org_role"]`. The v2 session token carries the user's system role for the active organization in the `o.rol` claim, and the Python SDK copies it to `payload["org_role"]` during `authenticate_request()`. The value is the role name without the `org:` prefix (e.g., `"admin"`, `"member"`), so compare as `state.payload["org_role"] == "admin"`. System **permissions** like `org:sys_memberships:manage` are the only things _not_ in the token — if you need those server-side, create a custom permission in the Dashboard and assign it to the system role. Source: [session tokens guide](https://clerk.com/docs/guides/sessions/session-tokens.md), [roles and permissions guide](https://clerk.com/docs/guides/organizations/control-access/roles-and-permissions.md).

### What's the difference between CLERK\_SECRET\_KEY and CLERK\_JWT\_KEY?

`CLERK_SECRET_KEY` (`sk_test_` or `sk_live_`) authorizes Backend API calls — creating users, listing sessions, etc. Treat it as a password. `CLERK_JWT_KEY` is the PEM-formatted public half of the signing keypair — it verifies JWT signatures locally and is safe to ship in any secret store alongside your other public identifiers. They're not interchangeable.

### Should I use python-jose or PyJWT for JWT verification?

PyJWT. `python-jose` carries [CVE-2024-33663](https://nvd.nist.gov/vuln/detail/CVE-2024-33663) (algorithm confusion, CVSS 6.5) and is effectively unmaintained. FastAPI itself [migrated to PyJWT + pwdlib in May 2024](https://github.com/fastapi/fastapi/pull/11589). With Clerk, you don't write verification code at all — `clerk-backend-api` wraps PyJWT internally.

### Does the Clerk Python SDK support async?

Yes. Every method on the `Clerk` class has a sync and async variant (`clerk.users.get(...)` and `clerk.users.get_async(...)`). The SDK is httpx-based, so async calls reuse connection pools properly. There is no separate `AsyncClerk` class.

### How do I handle Clerk webhooks in Python?

Install [`svix`](https://pypi.org/project/svix/), read the raw request body (`await request.body()` in FastAPI, `request.get_data()` in Flask), and call `Webhook(signing_secret).verify(body, headers)`. On success you get the parsed event — branch on `event["type"]` to handle `user.created`, `user.updated`, etc. Full example in Section 10d.

### Does Clerk support machine-to-machine (M2M) authentication in Python?

Yes. Pass `accepts_token=["m2m_token"]` into `AuthenticateRequestOptions` to restrict an endpoint to M2M tokens. Clerk supports both opaque M2M tokens (`m2m_`, revocable) and JWT M2M tokens (`mt_`, networkless, non-revocable until expiry). Note that the Python SDK's `accepts_token` defaults to `["any"]`, so always set it explicitly — `["session_token"]` for user endpoints, `["m2m_token"]` for service-to-service, or combine types like `["session_token", "api_key"]`. API keys (`ak_`, user-scoped) went [GA on 2026-04-17](https://clerk.com/changelog/2026-04-17-api-keys-ga.md).

### Can I use Clerk with a Python backend but a mobile frontend?

Yes. Mobile clients (iOS, Android, Expo) acquire session tokens via Clerk's mobile SDKs and send them as `Authorization: Bearer <token>`. The Python backend works identically — `authenticate_request()` doesn't care whether the token came from a browser or a phone.

### How do I test Clerk-protected endpoints locally?

Two patterns. For unit tests, override the auth dependency or patch the decorator to inject a fake user — shown in Sections 6h and 7h. For integration tests, sign in via a local frontend against a Clerk dev instance and use the real `getToken()` value in your test requests. Both are useful.

### How much does Clerk cost for a Python backend?

Pricing applies at the application level, not per SDK. Hobby (free) includes 50,000 MRUs. Pro starts at $20/month billed annually ($25 monthly) and adds MFA, passkeys, custom email templates, and 1 included Enterprise SSO connection. Business is $250/month billed annually ($300 monthly) and adds SOC 2 Report access plus HIPAA artifact access. Enterprise is custom-priced, adds HIPAA compliance with a BAA, and carries a 99.99% uptime SLA. See [clerk.com/pricing](https://clerk.com/pricing) for current numbers.
