Validating JWTs in FastAPI (Python)
Before you start, you'll need to make sure you're using a AWS Gateway JWT template with your Descope project. That way, your Descope JWTs will be OIDC compliant with the proper issuer and audience claims.
This guide is a step-by-step walkthrough of how to implement a custom Descope JWT authorizer in a Python FastAPI application.
To implement JWT validation, you'll need a reusable function or callable class that:
- Extracts and verifies the JWT from the request's Authorization header
- Returns
401 Unauthorized
if verification fails (missing, malformed, expired, or invalid token)
- Returns
- Enforces required scopes for scoped routes
- Returns
403 Forbidden
if the token lacks necessary scopes
- Returns
This guide shows you how to create those reusable functions and classes, and how to use them to secure your API routes.
Implementing the Descope JWT Authorizer
To validate Descope JWTs, you'll need to fetch the public key from Descope's JWKS endpoint.
If you're using a custom domain, replace the Base URL below with your own.
Your JWKS URL follows this format:
To fetch the public keys, we'll define a helper method _get_signing_key()
that wraps get_signing_key_from_jwt()
.
Setting Up Custom User-Agent for JWKS Fetching
In some cases you may need to configure a custom User-Agent
header before making JWKS requests.
This is because the PyJWKClient
internally uses Python's built-in urllib.request
to fetch the JWKS. By default, urllib
sends requests with a User-Agent
like Python-urllib/3.x
, which may be flagged or blocked by some CDNs or API gateways (as is the case with Descope's JWKS endpoint).
To avoid this, you can globally install a custom opener that adds a more typical User-Agent:
This ensures JWKS requests are treated as legitimate traffic and not blocked as bot or scanner activity.
It's recommended to place this in your main.py
or app startup script before any JWT validation occurs.
Validating the JWTs with the TokenVerifier
Once we've fetched the TokenVerifier
class and the _get_signing_key()
method, the next step is to decode and validate the JWT.
We'll implement a __call__
method inside our TokenVerifier
class to handle this process.
Extracting the Token from Incoming Requests
FastAPI provides a built-in way to extract and parse access tokens using the HTTPBearer()
dependency.
This reads the Bearer token from the HTTP authorization header from the incoming request and passes it into your function as an HTTPAuthorizationCredentials
object.
First, you can define the custom exceptions that will be used for error handling:
Next you can implement the __call__
method, which will be invoked by FastAPI whenever a protected route is accessed, to validate the incoming token.
You can find the Issuer URL under your default federated app settings here.
Enforcing Scopes
This section is optional. This is only necessary if you want to enforce scoped-based access control on your API routes.
In addition to validating your Descope access tokens in your FastAPI backend, you may also want to restrict access to specific API routes based on scopes/claims embedded in the JWT. You can read more scoping generally in our Inbound Apps docs page.
To do this, you can write a helper method _enforce_scopes()
, that checks whether the token's scope claim contains all the required scopes for the API route.
If any are missing, the request is will be deined with a 403 Forbidden
error, and optionally will include the missing scopes in the error message returned to the client.
Now, let's complete our __call__
function, which should now accept the SecurityScopes
parameter:
The SecurityScopes
parameter is automatically injected by FastAPI when you use the Security()
dependency. It contains information about what scopes are required for the current route, allowing your authorizer to enforce scope-based access control dynamically.
Protecting Routes Using the TokenVerifier
With the TokenVerifier
fully implemented, you can use it to secure any route in your FastAPI application.
Making a Route Private
To protect a route with authentication (i.e., require a valid JWT), use the TokenVerifier
as a dependency:
Adding Scope Validation to Private Routes
For more fine-grained access control via scopes, you can declare required scopes directly on the route.
This tells FastAPI to inject the decoded JWT token into the route, and enforce that it contains the read:messages
and write:messages
scopes: