Authentication
The privatisation of some endpoints (or even entire APIs) is a common requirement in many applications. This is especially true when dealing with sensitive data, such as Machine Learning APIs that process personal or financial information.
Flama provides a powerful, native, and highly integrated authentication system based on JSON Web Tokens (JWT). By leveraging Flama's architecture, you can secure your API endpoints and control access based on user permissions with just a few lines of code.
Understanding JWT
Before diving into the implementation within Flama, it is essential to understand what a JSON Web Token (JWT) is and how it functions as an authorisation mechanism.
We can start with the official definition given in the RFC 7519 document:
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure [...] enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.
In simple terms, JWT is an open standard that defines a way for transmitting information between two parties in the form of a JSON object. The information being transmitted is digitally signed to ensure its integrity and authenticity.
Authentication Flow
A prototypical JWT-based authentication flow looks like this:
- A user logs in to a system (providing credentials) and receives a JWT token.
- The user sends this token with every subsequent request to the server, typically in the
Authorizationoraccess_tokenheader. - The server verifies the token's signature and grants access to the requested resource if the token is valid.
- If the token is invalid or lacks the required permissions, the server denies access.
JWT tokens are not only useful to identify users; they can also be used to share information between different services securely via the payload. Because the signature is calculated using the header and the payload, the receiver can verify the integrity of the token and ensure it hasn't been altered in transit.
Anatomy of a JWT
A JWT is represented as a sequence of URL-safe parts separated by dots (.), with each part containing a base64url-encoded JSON object. Assuming we are using JWS (JSON Web Signature), which is the most common representation, a JWT consists of three parts:
The Header
The header contains metadata about the token and the cryptographic operations applied to it. The most commonly used fields are:
alg: The algorithm used to sign the token (e.g.,HS256,HS384,HS512, andEdDSA).typ: The media type of the token. It is recommended to have the valueJWTto indicate the object type.kid: The identity of the key that signed the token. An issuer that signs with more than one key sets it so a verifier knows which key to check against. It is the one field a verifier may read before it has verified anything, which is precisely why it belongs in the header rather than among the claims.
The Payload
The payload contains the claims (the actual information) that the token is carrying. According to the standard, claims are divided into three categories:
- Registered claims: A set of predefined, recommended claims. Common ones include
iss(issuer),sub(subject),aud(audience),exp(expiration time),nbf(not before), andiat(issued at). - Public claims: Claims defined by the user to share information between different services.
- Private claims: Custom claims intended to be shared between specific parties involved in the communication (e.g., internal user IDs or application-specific permissions).
The Signature
The signature ensures the integrity of the token and verifies that the sender is who it claims to be. It is generated using the encoded header, the encoded payload, the algorithm specified in the header, and a cryptographic key held by the issuer.
The alg value determines what kind of key that is. The HMAC algorithms (HS256, HS384, and HS512) compute a hash-based message authentication code from a single secret key, so whoever can verify a token can also issue one, and that key must never leave the services that mint tokens. EdDSA instead produces a digital signature with the private half of an Ed25519 keypair, and verification needs only the public half. Publishing that public key lets any party establish who issued a token without giving them the means to issue one, which makes EdDSA the better fit whenever tokens cross a trust boundary.
Authentication in Flama
To implement this secure flow, Flama relies on two of its core architectural features: Components and Middlewares. Understanding how they work is key to mastering Flama.
Dependency injection and components
As an application grows, so does the need to add more dependencies to it. Without Dependency Injection (DI), the Flama application class would have to create and manage all its dependencies internally, making it tightly coupled and harder to test. With DI, dependencies are provided from the outside.
In Flama, a Component is a building block for dependency injection. It encapsulates logic that determines how specific dependencies should be resolved when the application runs.
For authentication, we use the AccessTokenComponent. This component contains all the information and logic necessary for:
- Extracting a JWT from either the headers or cookies of an incoming request.
- Decoding the base64url-encoded strings.
- Validating its authenticity against your application's secret key.
Authentication Middleware
Middleware is a crucial concept that acts as a processing layer between the incoming requests from clients and the responses sent by the server. It functions as a gatekeeper that can inspect, modify, or act on requests before they reach your core application logic.
To handle JWTs, we use the built-in AuthenticationMiddleware. It is designed to ensure that only authorised users can access certain parts of your application. Here is exactly how it works under the hood:
- Handling Requests: Every time a request is made, the middleware intercepts it and checks if the route the request is trying to access requires any specific permissions.
- Permission Check: It extracts the required permissions from the route's
tags. If no permissions are required, the request passes through freely. - Resolution: If permissions are required, the middleware attempts to resolve a JWT token from the request using the
AccessTokenComponentwe registered. - Validating Permissions: Once resolved, the middleware checks the permissions embedded inside the token's payload against those required by the route. If the user’s permissions match or exceed the requirements, the request proceeds.
- Handling Errors: If the JWT token is missing, invalid, or lacks sufficient permissions, the middleware catches the exception and returns an appropriate error response (
401 Unauthorizedor403 Forbidden).
Implementing JWT Authentication
Now that we understand the theory and the architecture, let's build a fully functional, protected API.
Initialisation
First, we create our development environment by instantiating the Flama object. We must provide our secret key to the AccessTokenComponent and register the AuthenticationMiddleware.
import uuidfrom flama import Flamafrom flama.authentication import AccessTokenComponent, AuthenticationMiddleware
# The secret key used to sign and verify the tokens. Keep this safe!SECRET_KEY = uuid.UUID(int=0).bytes
app = Flama( components=[AccessTokenComponent(secret=SECRET_KEY)], middleware=[AuthenticationMiddleware()],)Generating tokens
To authenticate users, we need a login endpoint. Once a user provides valid credentials, we generate a JWT.
Flama provides the flama.authentication.jwt.jwt.JWT class to easily construct these tokens. For the AuthenticationMiddleware to automatically read the permissions, your custom claims must be placed inside a data dictionary within the payload.
from flama.authentication.jwt import jwt
@app.route("/login/", methods=["POST"])async def login(): # In a real app, verify user credentials (e.g., password hash) here. # Create the token structure jwt_token = jwt.JWT( {"alg": "HS256", "typ": "JWT"}, { "data": { "user_id": 123, # The permissions this user is granted: "permissions": ["read:secure"], }, "iat": 0, # Issued At }, )
# Encode and digitally sign the token using our SECRET_KEY token_string = jwt_token.encode(SECRET_KEY).decode()
return {"token": token_string}This example signs with a shared secret, which suits a service that both issues and verifies its own tokens. To sign
with a keypair instead, name EdDSA in the header and encode with the private key, as shown in
Signing with a keypair.
Protecting endpoints
With our middleware in place and tokens being generated, protecting an endpoint is remarkably simple. We use the tags argument in the route decorator and pass a list of required permissions.
@app.route("/secure/", methods=["GET"], tags={"permissions": ["read:secure"]})async def secure_info(): """ The AuthenticationMiddleware blocks any request that lacks a valid token with the 'read:secure' permission. """ return {"message": "You have accessed the secure vault!"}Accessing token data
Often, your business logic needs to know the identity of the user making the request. Because we registered the AccessTokenComponent, we can utilise dependency injection to request the AccessToken directly in our route handler's signature.
Flama will automatically parse the token and inject the object into your function.
from flama.authentication import AccessToken
@app.route("/me/", methods=["GET"], tags={"permissions": ["read:secure"]})async def current_user(token: AccessToken): # token.to_dict() returns the decoded header and payload dictionaries return {"your_token_data": token.to_dict()}Passing a token on
An application that calls another service on the caller's behalf has to present the caller's token, and an injected token carries the form it arrived as for exactly that:
@app.route("/report/", methods=["GET"], tags={"permissions": ["read:secure"]})async def report(token: AccessToken): async with Client(base_url="https://reports.example") as client: return await client.get("/", headers={"access_token": f"Bearer {token.raw.decode()}"})Passing the token on rather than signing a new one is what lets an application verify with a key it cannot sign
with — which is every application using a published key, since it holds only the public half. A token you construct
yourself has no raw to offer until you encode it, so the two cases stay easy to tell apart.
Signing with a keypair
A single secret is enough while the same service issues and verifies every token. Once a token has to be verified by
somebody other than its issuer, that arrangement stops working: anything able to check a token is equally able to mint
one. Signing with EdDSA separates the two halves, so a verifier holds only the public key.
Flama generates an Ed25519 keypair through the algorithm itself:
from flama.crypto.algorithms import EdDSAAlgorithm
private_key, public_key = EdDSAAlgorithm.generate()The private key signs, and never leaves the issuer. The public key is the one you publish, and it is all a verifier needs. Tokens are then issued exactly as before, naming the algorithm and the key in the header:
from flama.authentication.jwt import jwt
jwt_token = jwt.JWT( {"alg": "EdDSA", "typ": "JWT", "kid": "issuer-2026-09"}, {"data": {"user_id": 123, "permissions": ["read:secure"]}, "iat": 0},)
token_string = jwt_token.encode(private_key).decode()Verifying against many keys
An application that accepts tokens from a single issuer is given that issuer's key and nothing more. An application
serving several tenants, or one living through a key rotation, needs to choose a key per token — which is what the
kid in the header is for.
Instead of a fixed secret, a token component can be given a resolver: a callable that turns the identity in the header into the key to verify against. An issuer that publishes its public keys as a JWKS document — which is what almost every issuer does — needs nothing written by hand:
from flama import Flamafrom flama.authentication import AccessTokenComponent, AuthenticationMiddleware, JWKSResolver
app = Flama( components=[AccessTokenComponent(resolver=JWKSResolver("https://issuer.example/.well-known/jwks.json"))], middleware=[AuthenticationMiddleware()],)The resolver keeps the keys it fetches for ttl, so the issuer is not consulted on every request, and an identity it
has not seen prompts a fetch rather than a refusal — which is how a key the issuer has just started using becomes
usable without waiting for held keys to expire.
Fetches from any one document are spaced by fetch_interval, which matters more than it sounds: the resolver runs
before a signature is verified, since the key is what verifies it, so anyone able to reach your application can name
an identity that answers to nothing. Without that spacing each such request would become a request to the issuer.
By default an issuer that cannot be reached means a token cannot be verified, and the request fails. Giving a
stale_ttl instead lets a key already fetched go on being served for that long after it expires, so a brief outage at
the issuer does not become an outage in your application:
JWKSResolver("https://issuer.example/.well-known/jwks.json", stale_ttl=900)That is worth choosing deliberately rather than inheriting, because it is also the only thing that can carry a withdrawn key past its expiry: a key the issuer has stopped publishing stays usable for that long whenever the issuer is unreachable.
Where an issuer publishes a document per tenant rather than one for everything, the address can be derived from the identity itself:
JWKSResolver(lambda kid: f"https://issuer.example/{kid.split('.')[0]}/keys/")An application moving from a shared secret to published keys does not have to do both at once. Giving the resolver a
default key keeps tokens that name none verifiable, so the resolver can be deployed before anything about signing
changes, and the fallback removed once no such token can still be in flight:
JWKSResolver("https://issuer.example/.well-known/jwks.json", default=SECRET_KEY)Writing your own resolver
Anything callable will do, so a key held somewhere other than a JWKS document is no obstacle:
from flama.authentication import exceptions
KEYS = {"issuer-2026-09": public_key}
def resolve_key(kid): try: return KEYS[kid] except KeyError: raise exceptions.Unauthorized()Two details are worth knowing. A resolver may be asynchronous, because looking a key up can mean reading a database or
calling another service, and Flama awaits it either way. And raising Unauthorized is how a resolver reports
that no key answers to that identity, which the component turns into a 401; any other exception is treated as a
failure of the lookup itself rather than a rejected token.
A component takes either a secret or a resolver, never both.
Example
Here is the complete, runnable code demonstrating the entire flow, including a simulated client that tests the endpoints.
import asyncioimport uuidimport typing as t
from flama import Flamafrom flama.client import Clientfrom flama.authentication import ( AccessTokenComponent, AuthenticationMiddleware, AccessToken,)from flama.authentication.jwt import jwt
SECRET_KEY = uuid.UUID(int=0).bytes
app = Flama( components=[AccessTokenComponent(secret=SECRET_KEY)], middleware=[AuthenticationMiddleware()],)
# Public endpoints@app.route("/public/", methods=["GET"])async def public_info(): """Accessible by anyone.""" return {"message": "This is public information."}
@app.route("/login/", methods=["POST"])async def login(): """Simulates a login and generates a JWT.""" jwt_token = jwt.JWT( {"alg": "HS256", "typ": "JWT"}, { "data": { "user_id": 123, "permissions": ["read:secure"], }, "iat": 0, }, )
token_string = jwt_token.encode(SECRET_KEY).decode() return {"token": token_string}
# Protected endpoints@app.route("/secure/", methods=["GET"], tags={"permissions": ["read:secure"]})async def secure_info(): """Protected route.""" return {"message": "You have accessed the secure vault!"}
@app.route("/me/", methods=["GET"], tags={"permissions": ["read:secure"]})async def current_user(token: AccessToken): """Injects the token to access user data.""" return {"your_token_data": token.to_dict()}
async def main(): async with Client(app=app) as client: # Access Public Route print("\nAccessing /public/ without token...") response = await client.get("/public/") print(f" Status: {response.status_code}, Response: {response.json()}")
# Try Protected Route without Token print("\nAccessing /secure/ without token...") response = await client.get("/secure/") print(f" Status: {response.status_code}, Response: {response.json()}")
# Login to get Token print("\nLogging in via /login/ to get token...") response = await client.post("/login/") token_string = response.json()["token"] print(f" Received Token: {token_string[:20]}...")
# Access Protected Route WITH Token # Flama looks for the token in the 'access_token' header by default headers = {"access_token": f"Bearer {token_string}"}
print("\nAccessing /secure/ WITH token...") response = await client.get("/secure/", headers=headers) print(f" Status: {response.status_code}, Response: {response.json()}")
# Access Injection Route WITH Token print("\nAccessing /me/ to read token payload...") response = await client.get("/me/", headers=headers) print(f" Status: {response.status_code}, Response: {response.json()}")
if __name__ == "__main__": asyncio.run(main())Running the code above perfectly illustrates the gatekeeper pattern in action:
Accessing /public/ without token... Status: 200, Response: {'message': 'This is public information.'}
Accessing /secure/ without token... Status: 401, Response: {'detail': 'Unauthorized', 'error': 'HTTPException', 'status_code': 401}
Logging in via /login/ to get token... Received Token: eyJhbGciOiAiSFMyNTYi...
Accessing /secure/ WITH token... Status: 200, Response: {'message': 'You have accessed the secure vault!'}
Accessing /me/ to read token payload... Status: 200, Response: {'your_token_data': {'header': {'alg': 'HS256', 'typ': 'JWT'}, 'payload': {'data': {'user_id': 123, 'permissions': ['read:secure']}, 'iat': 0}}}Conclusions
Securing API endpoints is a fundamental requirement of modern software development, particularly for Machine Learning APIs that deal with sensitive or proprietary data.
Thanks to Flama's native implementation of JWTComponent and AuthenticationMiddleware, protecting routes and parsing authenticated user data becomes an effortless, declarative process. By combining simple route tags for permissions with powerful dependency injection for token data, you maintain clean, decoupled, and highly secure route handlers.