Authentication and Authorization in Falcon Applications
What Are Authentication and Authorization?
Authentication is the process of verifying who a user is. In web APIs, this typically involves validating credentials such as a username and password, an API key, or a JSON Web Token (JWT). Authorization, on the other hand, determines what an authenticated user is allowed to do. It enforces access control rules: for example, an admin user can delete resources while a regular user can only read them.
In the context of Falcon – a lightweight, high-performance Python web framework for building APIs – these two concepts are critical. Falcon itself does not ship with built-in authentication or authorization mechanisms. Instead, it provides flexible hooks (decorators) and middleware that allow developers to implement custom security logic. This design keeps the framework minimal while giving full control to the developer.
Why Authentication and Authorization Matter
- Data Protection – Unauthorized access can lead to data breaches, loss of sensitive information, and legal liabilities.
- User Accountability – Authenticated actions can be logged and audited, providing traceability.
- Granular Access Control – Authorization ensures that users can only perform operations for which they have permission, reducing the risk of accidental or malicious damage.
- Compliance – Many industry regulations (e.g., HIPAA, GDPR) require proper access controls.
- API Security – Public-facing APIs are prime targets; robust authentication and authorization are the first line of defense.
How to Implement Authentication in Falcon
Falcon provides two primary mechanisms to intercept requests: hooks (decorators applied to resource methods) and middleware (applied globally to every request). Both can be used to inspect and validate authentication tokens.
1. Token-Based Authentication Using a Decorator
A common pattern is to require a Bearer token in the Authorization header. Below is a simple implementation using a Falcon before hook.
import falcon
import secrets
# Simulated token store (in production, use a database)
VALID_TOKENS = {"abc123": "user1", "def456": "admin_user"}
def authenticate(req, resp, resource, params):
auth_header = req.get_header("Authorization")
if auth_header is None:
raise falcon.HTTPUnauthorized(
title="Authentication required",
description="Missing Authorization header",
scheme="Bearer"
)
# Expect "Bearer "
parts = auth_header.split()
if len(parts) != 2 or parts[0].lower() != "bearer":
raise falcon.HTTPUnauthorized(
title="Invalid authentication scheme",
description="Use Bearer token",
scheme="Bearer"
)
token = parts[1]
user = VALID_TOKENS.get(token)
if user is None:
raise falcon.HTTPUnauthorized(
title="Invalid token",
description="The provided token is not valid",
scheme="Bearer"
)
# Attach user info to the request context for later use
req.context.user = user
class ProtectedResource:
@falcon.before(authenticate)
def on_get(self, req, resp):
resp.media = {"message": f"Hello, {req.context.user}!"}
# Falcon app setup
app = falcon.App()
app.add_route("/protected", ProtectedResource())
In this example, the authenticate function is called before every method of ProtectedResource. It extracts the token, validates it against a simple dictionary, and raises falcon.HTTPUnauthorized if anything is wrong. On success, the user identifier is stored in req.context.user.
2. JWT Authentication with PyJWT
JSON Web Tokens are widely used for stateless authentication. Here we use the PyJWT library to decode and verify a token.
import falcon
import jwt
from datetime import datetime, timedelta
SECRET_KEY = "your-secret-key-change-in-production"
def create_jwt(user_id: str, role: str) -> str:
payload = {
"user_id": user_id,
"role": role,
"exp": datetime.utcnow() + timedelta(hours=1)
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def jwt_authenticate(req, resp, resource, params):
auth_header = req.get_header("Authorization")
if not auth_header:
raise falcon.HTTPUnauthorized(
title="Missing token",
description="Authorization header required"
)
# Expect "Bearer "
parts = auth_header.split()
if len(parts) != 2 or parts[0].lower() != "bearer":
raise falcon.HTTPUnauthorized(
title="Invalid header",
description="Format: Bearer "
)
token = parts[1]
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
req.context.user_id = payload["user_id"]
req.context.role = payload["role"]
except jwt.ExpiredSignatureError:
raise falcon.HTTPUnauthorized(
title="Token expired",
description="Please refresh your token"
)
except jwt.InvalidTokenError:
raise falcon.HTTPUnauthorized(
title="Invalid token",
description="Token verification failed"
)
class JWTProtectedResource:
@falcon.before(jwt_authenticate)
def on_get(self, req, resp):
resp.media = {
"message": f"User {req.context.user_id} authenticated",
"role": req.context.role
}
# Usage
app = falcon.App()
app.add_route("/jwt-protected", JWTProtectedResource())
The jwt_authenticate hook decodes the token, validates its signature and expiration, and populates the request context with user information. This pattern is stateless – no server-side session storage is required.
How to Implement Authorization in Falcon
Once the user is authenticated, authorization checks whether they have permission to perform a specific action. This can be done with additional hooks or within the resource method itself.
Role-Based Authorization
Assume that the JWT payload contains a role field (e.g., "admin", "user"). We can create an authorization decorator that checks the required role.
import falcon
from functools import wraps
def require_role(required_role: str):
def decorator(func):
@wraps(func)
def wrapper(self, req, resp, **kwargs):
# Assumes authentication hook already set req.context.role
if req.context.role != required_role:
raise falcon.HTTPForbidden(
title="Insufficient permissions",
description=f"Requires role: {required_role}"
)
return func(self, req, resp, **kwargs)
return wrapper
return decorator
class AdminResource:
@falcon.before(jwt_authenticate) # authenticate first
@require_role("admin")
def on_get(self, req, resp):
resp.media = {"message": "Welcome, admin!"}
app = falcon.App()
app.add_route("/admin", AdminResource())
In this example, @require_role("admin") is applied after the authentication hook. It checks the