Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

FastAPI provides bearer-token dependencies and OpenAPI security declarations; PyJWT verifies the token. A secure API must obtain signing keys from the trusted issuer’s JWKS, allow only configured signing algorithms, validate the expected issuer and audience, and check authorization scopes separately from token validity.

What FastAPI’s OIDC support does—and does not do

FastAPI’s openIdConnect security scheme describes OpenID Connect discovery in OpenAPI. Its security dependencies also let an application declare OAuth2 scopes in its API documentation. These features help describe and wire security into routes; they do not, by themselves, perform provider-specific JWT verification or decide what an authenticated user may do.

For a bearer access token, your application still needs to obtain the issuer’s metadata, resolve the token’s signing key, validate its signature and claims, and apply its own authorization policy. Treat authentication (“is this token valid for this API?”) and authorization (“may this principal perform this operation?”) as separate checks.

Install the cryptographic dependency

For RSA or ECDSA signatures, install PyJWT with its cryptography extra:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
pip install "pyjwt[crypto]" fastapi httpx

FastAPI’s JWT guidance specifically recommends pyjwt[crypto] when using digital-signature algorithms such as RSA or ECDSA. Use an algorithm actually supported and configured by your identity provider; the example below uses RS256 as an example configuration, not a universal default.

Configure discovery and JWKS from a trusted issuer

Start with an issuer URL you control through configuration, not one taken from the incoming token. Fetch that issuer’s OpenID Connect discovery document over TLS, verify that its advertised issuer matches your configured issuer, and use its jwks_uri to obtain signing keys. The API audience is a separate configured value identifying the resource server that should accept the token.

For an OAuth JWT access token, RFC 9068 recommends asymmetric signing and describes publishing the expected issuer and a jwks_uri, directly or through OIDC discovery. With asymmetric keys, an API can verify provider signatures using published public keys rather than sharing a signing secret with the provider.

import httpx
from jwt import PyJWKClient

ISSUER = "https://identity.example"
DISCOVERY_URL = "https://identity.example/.well-known/openid-configuration"
API_AUDIENCE = "https://api.example"
ALLOWED_ALGORITHMS = ["RS256"]  # Match the provider and your policy.

response = httpx.get(DISCOVERY_URL, timeout=5.0)
response.raise_for_status()
metadata = response.json()

if metadata.get("issuer") != ISSUER:
    raise RuntimeError("OIDC discovery issuer does not match configured issuer")

jwks_client = PyJWKClient(metadata["jwks_uri"])

The discovery URL format can depend on the issuer’s deployment, so configure the provider’s documented discovery URL rather than assuming every issuer uses the same path. Perform discovery during controlled startup or configuration refresh, not by trusting a URL in a request. In production, set bounded timeouts, cache metadata and keys deliberately, and log refresh failures without logging bearer tokens.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Validate the signature, issuer, audience, and required claims

Use the untrusted token only to locate a candidate key by its kid. Then verify the signature and claims with PyJWT, passing an explicit algorithm allowlist and the expected issuer and audience. PyJWT warns against deriving algorithms from the token header or any other attacker-influenced value.

import jwt


def validate_access_token(token: str) -> dict:
    signing_key = jwks_client.get_signing_key_from_jwt(token)
    return jwt.decode(
        token,
        signing_key.key,
        algorithms=ALLOWED_ALGORITHMS,
        issuer=ISSUER,
        audience=API_AUDIENCE,
        options={"require": ["exp", "iss", "aud"]},
    )

Keep the algorithm list fixed in trusted application configuration. A JWT header naming a different algorithm is not permission to accept it. The key must come from the configured issuer’s JWKS, not from a token-provided key URL. PyJWT’s PyJWKClient is designed to retrieve signing keys from a JWKS endpoint and select the key corresponding to the JWT.

The example requires expiration, issuer, and audience claims and asks PyJWT to validate issuer and audience values. Decide whether your provider’s access-token profile also requires other claims, such as a subject or client identifier, and enforce those explicitly for your application. Configure clock-skew leeway only when operationally justified; it changes how long a token near its time boundary may be accepted.

Turn verified claims into a principal, then authorize scopes

After validation, map only the claims your application needs into a typed principal. Scope representation varies by issuer and token profile; for example, some tokens use a space-delimited scope string. Normalize the provider’s documented representation once, then compare the resulting permissions with the route’s requirements and your own tenant, client, subject, and business rules.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from dataclasses import dataclass

@dataclass
class Principal:
    subject: str
    scopes: frozenset[str]


def principal_from_claims(claims: dict) -> Principal:
    subject = claims.get("sub")
    if not isinstance(subject, str) or not subject:
        raise ValueError("Token has no usable subject")

    raw_scope = claims.get("scope", "")
    scopes = frozenset(raw_scope.split()) if isinstance(raw_scope, str) else frozenset()
    return Principal(subject=subject, scopes=scopes)


def require_scopes(principal: Principal, required: set[str]) -> None:
    if not required.issubset(principal.scopes):
        raise PermissionError("Insufficient scope")

In route dependencies, use FastAPI’s Security mechanism to declare scopes so they can be represented in OpenAPI, and check those required scopes against the validated principal at request time. Use an OAuth2 security scheme that describes your provider’s actual authorization and token endpoints when you need scopes documented as OAuth2 scopes; a generic bearer scheme does not express the same scope contract. Do not treat scopes requested by a client as proof that the issuer granted them. FastAPI’s scope guidance emphasizes that applications must ensure scopes are allowed before adding them to a token.

A scope check alone may not be sufficient: an operation may also require a particular client, tenant membership, resource ownership, or application role. Make those checks against trusted validated claims and application data, not request parameters supplied by the caller.

Handle authentication failures and key rotation

Return an authentication failure for a missing or malformed bearer token, an expired token, an invalid signature, a wrong issuer or audience, an unsupported algorithm, or an unavailable matching key. Do not turn a failed verification into an anonymous success. Keep authorization denials distinct from token-validation failures so clients and logs can distinguish “not authenticated” from “authenticated but not permitted.”

Providers rotate keys by publishing signing keys through JWKS. Cache keys to avoid fetching the set for every request, but refresh when a token presents an unfamiliar kid; bound refresh frequency and cache lifetime so an attacker cannot force unbounded network requests. If the issuer or JWKS endpoint is unavailable, apply an explicit availability policy: a previously cached valid key may allow verification, but an API must not accept an unverifiable token as a fallback. Monitor discovery/JWKS refresh errors and clock synchronization, and never log raw authorization headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Choose an issuer based on operational fit

PyJWT names Auth0 and Okta as examples of providers that publish JWKS endpoints. That technical fact is not a comparison of their plans, terms, or current availability. Whether you use a managed provider or operate an issuer yourself, assess the same security and operations questions:

  • Discovery and JWKS: Does the issuer publish reliable metadata and signing keys in a format your API can consume?
  • Key rotation and algorithms: How are signing keys rotated, which algorithms are available, and how will your allowlist stay aligned with provider configuration?
  • Claims and policy control: Can you issue the claims, scopes, and tenant identifiers your API needs, while ensuring they reflect actual authorization?
  • Integration effort: What provider SDKs, operational knowledge, and FastAPI dependency code are required?
  • Availability and incident response: Who monitors the issuer, handles outages, and responds to compromised keys or credentials?
  • Data residency and total operating cost: Where are identity data and logs processed, and what are the full operating costs for the chosen deployment?

A managed identity service can reduce the work of operating an issuer, while a self-hosted issuer may offer more direct control. Neither option removes the API’s responsibility to validate access tokens and authorize requests correctly.

Keep bearer-token contents non-sensitive

A signed JWT is not encrypted. Its payload is readable by anyone holding the token, even though changing the payload invalidates the signature. Keep claims minimal; do not put passwords, secrets, or sensitive records in a bearer token on the assumption that base64url encoding hides them. Treat the token itself as a credential and protect it in transit and storage.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.