Advanced TopicsAuthentication
Advanced Topics~ 9 min read

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 Authorization or access_token header.
  • 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).
  • typ: The media type of the token. It is recommended to have the value JWT to indicate the object type.

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), and iat (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 secret cryptographic key known only to the server.

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:

  1. Extracting a JWT from either the headers or cookies of an incoming request.
  2. Decoding the base64url-encoded strings.
  3. 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 AccessTokenComponent we 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 Unauthorized or 403 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.middleware import Middlewarefrom 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=[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( header={"alg": "HS256", "typ": "JWT"}, payload={ "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}

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()}

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.middleware import Middlewarefrom 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=[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.