Authentication

flarchitect provides several helpers so you can secure your API quickly. Enable one or more strategies via API_AUTHENTICATE_METHOD. Available methods are jwt, basic, api_key and custom. Each example below uses the common setup defined in demo/authentication/app_base.py. Runnable snippets demonstrating each strategy live in the project repository: jwt_auth.py, basic_auth.py and api_key_auth.py. You can also protect routes based on user roles using the Role-based access decorator.

Error responses

Missing or invalid credentials return a 401 response:

{
  "errors": {"error": "Authorization header missing"},
  "status_code": 401,
  "value": null
}

Expired tokens also yield a 401:

{
  "errors": {"error": "Token has expired"},
  "status_code": 401,
  "value": null
}

Refresh failures, such as an invalid refresh token, respond with 403:

{
  "errors": {"error": "Invalid or expired refresh token"},
  "status_code": 403,
  "value": null
}

JWT authentication

JSON Web Tokens (JWT) allow a client to prove their identity by including a signed token with every request. The token typically contains the user’s ID and an expiry timestamp. Clients obtain an access/refresh pair from a login endpoint and then send the access token in the Authorization header:

Authorization: Bearer <access-token>

To enable JWT support you must provide ACCESS_SECRET_KEY and REFRESH_SECRET_KEY values along with a user model. A minimal configuration looks like:

class Config(BaseConfig):
    API_AUTHENTICATE_METHOD = ["jwt"]
    ACCESS_SECRET_KEY = "access-secret"
    REFRESH_SECRET_KEY = "refresh-secret"
    API_USER_MODEL = User
    API_USER_LOOKUP_FIELD = "username"
    API_CREDENTIAL_CHECK_METHOD = "check_password"

demo/authentication/jwt_auth.py contains a full example including a login route:

from flask import abort, request
from flask_jwt_extended import (
    create_access_token,
    create_refresh_token,
)

@app.post("/login")
def login():
    user = User.query.filter_by(username=request.json["username"]).first()
    if user and user.check_password(request.json["password"]):
        return {
            "access_token": create_access_token(identity=user.id),
            "refresh_token": create_refresh_token(identity=user.id),
        }
    abort(401)

Send subsequent requests with the Authorization header set to the access token and refresh it with the refresh token when it expires.

Basic authentication

HTTP Basic Auth is the most straightforward option. The client includes a username and password in the Authorization header on every request. The credentials are base64 encoded but otherwise sent in plain text, so HTTPS is strongly recommended.

Provide a lookup field and password check method on your user model:

class Config(BaseConfig):
    API_AUTHENTICATE_METHOD = ["basic"]
    API_USER_MODEL = User
    API_USER_LOOKUP_FIELD = "username"
    API_CREDENTIAL_CHECK_METHOD = "check_password"

flarchitect also provides a simple login route for this strategy. POST to /auth/login with a Basic Authorization header to verify credentials and receive basic user information:

curl -X POST -u username:password http://localhost:5000/auth/login

You can then access endpoints with tools such as curl:

curl -u username:password http://localhost:5000/api/books

See demo/authentication/basic_auth.py for a runnable snippet.

API key authentication

API key auth associates a user with a single token. Clients send the token in each request, usually via a header like X-API-Key or as a query string parameter. flarchitect passes the token to a function you provide, and the function returns the matching user. If you store hashed tokens on the model, set API_CREDENTIAL_HASH_FIELD to the attribute holding the hash so flarchitect can validate keys.

Attach a function that accepts an API key and returns a user. The function can also call set_current_user:

def lookup_user_by_token(token: str) -> User | None:
    user = User.query.filter_by(api_key=token).first()
    if user:
        set_current_user(user)
    return user

class Config(BaseConfig):
    API_AUTHENTICATE_METHOD = ["api_key"]
    API_KEY_AUTH_AND_RETURN_METHOD = staticmethod(lookup_user_by_token)

When this method is enabled flarchitect exposes a companion login route. POST an Api-Key Authorization header to /auth/login to validate the key and retrieve basic user details:

curl -X POST -H "Authorization: Api-Key <token>" http://localhost:5000/auth/login

Example request:

curl -H "X-API-Key: <token>" http://localhost:5000/api/books

See demo/authentication/api_key_auth.py for more detail.

Custom authentication

For complete control supply your own callable. This method lets you support any authentication strategy you like: session cookies, HMAC signatures or third-party OAuth flows. Your callable should return True on success and may call set_current_user to attach the authenticated user to the request.

def custom_auth() -> bool:
    token = request.headers.get("X-Token", "")
    user = User.query.filter_by(api_key=token).first()
    if user:
        set_current_user(user)
        return True
    return False

class Config(BaseConfig):
    API_AUTHENTICATE_METHOD = ["custom"]
    API_CUSTOM_AUTH = staticmethod(custom_auth)

Clients can then call your API with whatever headers your function expects:

curl -H "X-Token: <token>" http://localhost:5000/api/books

See demo/authentication/custom_auth.py for this approach in context.

Role-based access

Use the roles_required decorator to allow only users with specific roles to access an endpoint. The decorator checks the roles attribute on current_user which is populated by the active authentication method.

from flarchitect.authentication import roles_required

@app.get("/admin")
@roles_required("admin")
def admin_dashboard():
    return {"status": "ok"}

You can require multiple roles by passing more than one name:

@roles_required("admin", "editor")
def update_post():
    ...

Ensure your user model exposes a list of role names, for example User.roles = ["admin", "editor"]. If the authenticated user lacks any of the required roles—or if no user is authenticated—a 403 response is raised.

Troubleshooting