How to Configure Payload Encryption
Framework M supports four payload encryption modes. This guide explains how to enable and tune them.
Choose a Mode
| Mode | What is encrypted | Best for |
|---|---|---|
disabled | Nothing | Local development, CI, trusted internal services |
selective | Routes you configure (auth routes by default) | Standard SaaS deployments |
metadata | DocType fields flagged encrypted or pii | Granular PII protection in Desk forms |
strict | All mutating requests (POST, PUT, PATCH) | Fintech, zero-trust enterprise |
Global Configuration
Set the mode in pyproject.toml or your web_config.toml:
[tool.framework_m.security.payload_encryption]
mode = "selective"
jwks_uri = "/api/v1/security/jwks"
# Optional: extend the default selective routes
routes = [
"/api/v1/auth/login",
"/api/v1/auth/register",
"/api/v1/payments/charge",
]
When routes is omitted in selective mode, the framework defaults to all authentication endpoints:
/api/v1/auth/login/api/v1/auth/register/api/v1/auth/forgot-password/api/v1/auth/reset-password/api/v1/auth/otp/api/v1/auth/magic-link
Flagging DocType Fields (metadata mode)
In metadata mode, Desk forms automatically encrypt fields marked in the DocType schema:
from pydantic import Field
from framework_m_core.domain.base_doctype import BaseDocType
class PatientRecord(BaseDocType):
name: str = Field(json_schema_extra={"pii": True})
email: str = Field(json_schema_extra={"pii": True, "encrypted": True})
ssn: str = Field(json_schema_extra={"encrypted": True})
diagnosis_code: str = Field()
The metadata router exposes these flags in GET /meta/PatientRecord. The Desk useFormController reads the schema and passes it to the data provider, which encrypts the payload when any flagged field is present.
Use json_schema_extra flags on DocType models only. Litestar request/response DTOs (such as LoginRequest) cannot carry unknown json_schema_extra keys because Litestar's OpenAPI generator rejects them. Auth routes are protected by selective mode route configuration instead.
Behavior Matrix
| Global Mode | encrypted=True | pii=True | Result |
|---|---|---|---|
disabled | Ignored | Ignored | Cleartext JSON |
selective | Encrypted if route is in the list | Treated as encrypted=True for listed routes | Route list wins |
metadata | Always encrypted | Treated as encrypted=True | PII fields encrypted automatically |
strict | Always encrypted for mutations | Always encrypted for mutations | All POST/PUT/PATCH bodies encrypted |
Verifying Encryption
- Start the application with
mode = "selective". - Open the browser DevTools Network tab.
- Submit a login form.
- The
POST /api/v1/auth/loginrequest should have:Content-Type: application/jose- A JWE envelope body instead of cleartext JSON.
Disabling for Development
Use mode = "disabled" (the default) so Chrome DevTools and Postman show cleartext JSON:
[tool.framework_m.security.payload_encryption]
mode = "disabled"
Never use disabled in production for sensitive endpoints.