Role Based Access Management¶
JWT Token¶
AppKernel uses JWT tokens (RS256) for authentication and authorisation. To issue tokens, add the
IdentityMixin to a model that has an id and a roles field:
from typing import Annotated
from appkernel import Model, MongoRepository, IdentityMixin, Required, Generator, create_uuid_generator
class User(Model, MongoRepository, IdentityMixin):
id: Annotated[str | None, Required(), Generator(create_uuid_generator('U'))] = None
roles: list[str] | None = None
With this, each User instance exposes an auth_token property:
print(f'token: {user.auth_token}')
The token is digitally signed with RS256 and includes an aud claim set to the
app_id passed to AppKernelEngine. Tokens issued by one service are rejected
by all other services, preventing cross-service token replay.
Setup¶
JWT tokens require an RSA key pair. Generate one using OpenSSL:
# Generate private key
openssl genpkey -out appkernel.pem -algorithm rsa -pkeyopt rsa_keygen_bits:2048
# Derive the public key
openssl rsa -in appkernel.pem -out appkernel.pub -pubout
By default AppKernel looks for these files at {config-folder}/keys/appkernel.pem
and {config-folder}/keys/appkernel.pub. See Key Path Configuration to
override this location.
Key Path Configuration¶
The private and public key paths are resolved in the following priority order:
1. Environment variables (highest priority)
Set APPKERNEL_PRIVATE_KEY_PATH and APPKERNEL_PUBLIC_KEY_PATH to absolute
file paths. This is the recommended approach for production and container deployments:
export APPKERNEL_PRIVATE_KEY_PATH=/run/secrets/appkernel.pem
export APPKERNEL_PUBLIC_KEY_PATH=/run/secrets/appkernel.pub
With Docker or Kubernetes, mount the key files as secrets and point the env vars at the mount path.
2. Configuration file (cfg.yml)
Add private_key_path and public_key_path under appkernel.security:
appkernel:
security:
private_key_path: /etc/myapp/keys/private.pem
public_key_path: /etc/myapp/keys/public.pub
3. Default path (fallback)
If neither env vars nor cfg.yml entries are set, AppKernel loads keys from:
{config-folder}/keys/appkernel.pem
{config-folder}/keys/appkernel.pub
where {config-folder} is the cfg_dir argument passed to AppKernelEngine.
CORS¶
Cross-Origin Resource Sharing (CORS) controls whether browser-based JavaScript
on one origin (e.g. https://app.example.com) may call an API on a different
origin (e.g. https://api.example.com). Without CORS headers the browser
blocks the response, even though the server processed the request.
AppKernel adds no CORS headers by default. Call enable_cors() to opt in:
from appkernel import AppKernelEngine, CorsConfig
kernel = AppKernelEngine('my-app', cfg_dir='./config')
kernel.enable_security()
kernel.enable_rate_limiting()
# enable_cors() must be called LAST so it executes first and handles
# OPTIONS preflight before security / rate-limiting checks run.
kernel.enable_cors(CorsConfig(
allow_origins=['https://app.example.com'],
))
Middleware ordering¶
FastAPI / Starlette executes middleware in reverse registration order — the
last middleware added runs first. enable_cors() must therefore be called
after enable_security() and enable_rate_limiting() so that:
CORS middleware handles preflight
OPTIONSrequests (and returns 200) before the security middleware rejects them as unauthenticated.CORS headers are injected on every response, including 4xx and 5xx errors.
CorsConfig reference¶
Parameter |
Default |
Description |
|---|---|---|
|
|
Permitted origin list.
Use |
|
|
Permitted HTTP methods. |
|
|
Permitted request headers. |
|
|
Set |
|
|
Response headers JS may read. |
|
|
Preflight cache lifetime in seconds. |
Security notes¶
Never use
allow_origins=['*']in production — it allows any website to read API responses from a logged-in user’s browser session.allow_origins=['*']combined withallow_credentials=Trueis rejected at startup withValueError(browsers refuse this combination anyway).Restrict
allow_headersto the minimum your frontend actually sends. Broad header allowlists increase the attack surface for header injection.
CSRF¶
Cross-Site Request Forgery (CSRF) tricks a victim’s browser into sending an authenticated request to your API from a malicious page. The attack works because browsers automatically attach cookies to cross-origin requests.
AppKernel’s current auth model (JWT in the Authorization header)
is not vulnerable to CSRF. Browsers do not auto-attach custom headers to
cross-origin requests, so an attacker’s page cannot issue an authenticated
request without first stealing the token (which would be XSS, a different
attack). No CSRF protection is required for header-based JWT APIs.
CSRF protection is required only if you introduce cookie-based authentication. If you add cookie sessions or store JWTs in cookies, adopt one of the following mitigations before the feature ships:
Option A — SameSite cookie attribute (recommended for most cases)
Set SameSite=Strict (or SameSite=Lax) on the auth cookie:
# Example when setting a cookie in a FastAPI response
response.set_cookie(
key='session',
value=token,
httponly=True,
samesite='strict', # browser refuses to send on cross-origin requests
secure=True, # HTTPS only
)
SameSite=Strict blocks the cookie on all cross-origin requests, including
OAuth redirect flows. Use SameSite=Lax if you need OAuth to work; it still
blocks cross-origin POST/PUT/DELETE but permits top-level navigations.
Option B — Double-submit cookie pattern
Alongside the HttpOnly session cookie, issue a second readable CSRF token
cookie. The client echoes this token in a custom request header (e.g.
X-CSRF-Token). The server rejects any state-changing request that lacks
the matching header. This is compatible with OAuth flows but adds a round-trip.
Option C — Origin / Referer validation
In a middleware, reject state-changing requests (POST, PUT, PATCH, DELETE)
whose Origin or Referer header does not match the known API hostname.
Lightweight and stateless but not universally reliable (some proxies strip
Referer).
Decision matrix¶
Auth mechanism |
CSRF risk |
Recommended mitigation |
|---|---|---|
JWT in |
None |
Nothing required |
JWT in |
High |
|
Session cookie (OAuth, etc.) |
High |
|