KMS signing
JwtPlugin accepts a signer_factory callable so production deployments
can keep private keys inside a hardware-backed KMS rather than the
application database. The factory receives the plugin's JwksRegistry and
must return any object satisfying the KmsSigner Protocol:
Fastauth's local JWT/JWKS encryption and signing path has not been independently audited. For high-stakes production deployments, prefer a KMS/HSM-backed signer and run an application-specific security review.
from typing import Any
from fastauth.security.jwt import KmsSigner
class CloudKmsSigner:
def __init__(self, registry: JwksRegistry, key_id: str) -> None:
self.registry = registry
self.key_id = key_id
async def sign(self, *, header: dict[str, Any], payload: dict[str, Any]) -> str:
# Build the JWS encoding, then hand the digest to your KMS API.
...
Wire the factory into the plugin:
from pydantic import SecretStr
from fastauth import FastAuth, FastAuthOptions
from fastauth.database import memory
from fastauth.plugins.jwt import JwtOptions
from fastauth import email_password, jwt
auth = FastAuth(
FastAuthOptions(
secret_key=SecretStr("replace-me-with-your-application-secret"),
database=memory(),
),
plugins=[
email_password(),
jwt(
JwtOptions(disable_private_key_encryption=True),
signer_factory=lambda registry: CloudKmsSigner(
registry,
key_id="projects/.../fastauth",
),
),
],
)
Setting disable_private_key_encryption=True tells fastauth not to AES-GCM
encrypt the stored private key — when KMS is responsible for signing, the
plugin still tracks the public JWKS but the in-database private material can
be a placeholder.
Local fallback
LocalKmsSigner is the default signer; it loads the encrypted private key
from the jwks_keys collection, decrypts it with a KEK derived from
options.secret_key, and signs in-process. This is appropriate for
development and small deployments.