Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions docs/configuration/secret-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# Secret Key Management

`authpaseto_secret_key` is the root of trust for all `local` tokens. If an attacker
reads this key, they can mint valid tokens and impersonate any subject in your
system until the key is rotated and previously issued tokens are invalidated.

This guide explains how to:

1. Generate a cryptographically strong key.
2. Store it in a secure system instead of source code.
3. Retrieve it safely at runtime.
4. Rotate it with minimal downtime.

## Why this matters

For `local` PASETO, your secret key is used for authenticated encryption.
Compromise impact is high:

- **Token forgery:** attackers can create valid access or refresh tokens.
- **Privilege escalation:** forged tokens can carry arbitrary claims.
- **Persistence:** compromise may remain active until keys rotate.

Treat this key like a database credential with admin scope, or better.

## Key generation requirements

Use at least **32 random bytes** from a cryptographically secure RNG. For
storage and transport, encode bytes as URL-safe base64.

```python
import base64
import secrets


def generate_paseto_secret_key() -> str:
"""Return a new URL-safe base64 key for `authpaseto_secret_key`."""
raw_key = secrets.token_bytes(32)
return base64.urlsafe_b64encode(raw_key).decode("utf-8")
```

Avoid:

- Hardcoded strings such as `"secret"`.
- Password-like values typed by humans.
- Reusing keys across environments.

Generate unique keys per environment (`dev`, `staging`, `prod`) and per tenant
when you need strict cryptographic isolation.

## Storage and retrieval options

### 1) Environment variable (baseline)

Store only the generated random key value in an environment variable such as
`AUTHPASETO_SECRET_KEY`.

**Advantages**

- Easy to adopt.
- Works with containers and most deployment systems.

**Disadvantages**

- Exposed to process-level introspection.
- Rotation usually needs restarts.
- Weak controls if your deployment platform lacks secret management.

### 2) Encrypted file + Fernet envelope encryption

Store an encrypted key blob on disk and decrypt it at startup with a separate
master key (for example from your orchestrator secret store).

**Advantages**

- At-rest encryption for file-based deployments.
- Easy local/offline operation.

**Disadvantages**

- You must still protect and rotate the Fernet master key.
- Adds envelope-encryption operational complexity.

### 3) TPM-backed unsealing (`tpm2-pytss`)

Seal the PASETO key to TPM state (PCR policy), then unseal only on trusted
hosts and expected boot conditions.

**Advantages**

- Hardware-backed protection.
- Strong resistance to key extraction from disk images.
- Can bind decryption to platform integrity.

**Disadvantages**

- Platform-specific provisioning and recovery workflows.
- Hardware lifecycle and replacement complexity.
- More operational overhead than software-only options.

### 4) HashiCorp Vault (transit/kv)

Keep key material in Vault and retrieve short-lived secrets at startup (or on a
refresh schedule). Prefer AppRole, Kubernetes auth, or cloud IAM auth.

**Advantages**

- Centralized access control and auditing.
- Strong policy model and dynamic credentials.
- Good rotation workflows.

**Disadvantages**

- Requires high-availability Vault operations.
- New dependency in your auth critical path.
- Misconfigured policies can still leak secrets.

### 5) Cloud secret managers (AWS/GCP/Azure)

Use managed secrets services (for example AWS Secrets Manager, GCP Secret
Manager, or Azure Key Vault) with workload identity.

**Advantages**

- Managed durability/HA and IAM integration.
- Native rotation tooling and audit logs.

**Disadvantages**

- Cloud lock-in.
- Latency/network dependency if fetched at request time.

### 6) Keycloak (identity provider integration)

Keycloak is useful for externalizing identity and token issuance, but it is not
a full secret-management system for generic application keys.

**Advantages**

- Centralized identity and access model.
- Useful when you already delegate authn/authz to Keycloak.

**Disadvantages**

- Not designed as a general-purpose secret vault.
- Limited secret lifecycle controls compared to Vault/KMS products.
- Usually better paired with a dedicated secret manager.

## Recommended loading pattern

- Retrieve the secret once during startup.
- Keep it in memory only (avoid writing plaintext to logs/files).
- Fail fast if retrieval fails.
- Cache with explicit refresh policy only if your backend supports safe rotation.

```python
import os

from fastapi_paseto import AuthPASETO


def load_secret_from_env() -> str:
"""Read a required PASETO secret from environment variables."""
secret = os.getenv("AUTHPASETO_SECRET_KEY")
if not secret:
msg = "AUTHPASETO_SECRET_KEY is not set"
raise RuntimeError(msg)
return secret


@AuthPASETO.load_config
def get_config() -> dict[str, str]:
"""Provide secure runtime configuration to FastAPI PASETO."""
return {"authpaseto_secret_key": load_secret_from_env()}
```

## Rotation strategy

Use staged rotation with overlap:

1. Generate a new key in your secret manager.
2. Deploy services that can validate old tokens while issuing with the new key
(or use short token TTLs and coordinated cutover).
3. Wait for old access/refresh windows to expire.
4. Revoke and remove the previous key.

For high-security environments, automate periodic key rotation and include
incident-driven emergency rotation playbooks.
1 change: 1 addition & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ from example file to the relevant guide.
| `examples/footer_assertion.py` | Footers and implicit assertions | [Footers and Assertions](advanced-usage/footer-assertion.md) |
| `examples/overrides.py` | Route-level transport overrides | [Per-route Overrides](advanced-usage/overrides.md) |
| `examples/validation.py` | Issuer, audience, base64, and custom token types | [Validation and Custom Types](advanced-usage/validation.md) |
| `examples/secure_secret_management.py` | Secure key generation and retrieval patterns | [Secret Key Management](configuration/secret-management.md) |
| `examples/generate_doc.py` | Manual OpenAPI customization | [Generate Documentation](advanced-usage/generate-docs.md) |
| `examples/multiple_files/` | Multi-module application layout | [Bigger Applications](advanced-usage/bigger-app.md) |
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,6 @@ def get_config():
- Start with [Basic Usage](usage/basic.md)
- See [JSON Body Tokens](usage/json.md) for HTTP body transport
- See [WebSocket Usage](usage/websocket.md) for header and query auth
- See [Secret Key Management](configuration/secret-management.md) for generation, storage, and rotation guidance
- See [Examples](examples.md) for the full example catalog
- See [API Documentation](api-doc.md) for the full callable reference
77 changes: 77 additions & 0 deletions examples/secure_secret_management.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Example secret loading patterns for `authpaseto_secret_key`.

This module demonstrates secure key generation and retrieval options that can be
adapted to environment variables, Fernet-encrypted local files, and TPM-backed
unsealing.
"""

import base64
import os
import secrets
from pathlib import Path

from cryptography.fernet import Fernet

from fastapi_paseto import AuthPASETO


def generate_paseto_secret_key() -> str:
"""Generate a URL-safe base64 key for `authpaseto_secret_key`.

Returns:
Newly generated key encoded as UTF-8 text.
"""
raw_key = secrets.token_bytes(32)
return base64.urlsafe_b64encode(raw_key).decode("utf-8")


def load_secret_from_env() -> str:
"""Load `AUTHPASETO_SECRET_KEY` and fail fast if it is unset.

Raises:
RuntimeError: If the environment variable is missing.

Returns:
Secret key string suitable for `authpaseto_secret_key`.
"""
secret = os.getenv("AUTHPASETO_SECRET_KEY")
if secret:
return secret
msg = "AUTHPASETO_SECRET_KEY is not set"
raise RuntimeError(msg)


def decrypt_secret_with_fernet(encrypted_path: Path, fernet_key: str) -> str:
"""Decrypt an encrypted secret blob with Fernet.

Args:
encrypted_path: Path to encrypted key bytes.
fernet_key: Base64-encoded Fernet key from a secure source.

Returns:
Decrypted secret key text for `authpaseto_secret_key`.
"""
encrypted_bytes = encrypted_path.read_bytes()
decrypted_bytes = Fernet(fernet_key.encode("utf-8")).decrypt(encrypted_bytes)
return decrypted_bytes.decode("utf-8")


def load_secret_from_tpm() -> str:
"""Placeholder showing where TPM unseal integration should happen.

Replace this function with your `tpm2-pytss` policy session and unseal flow.

Returns:
Secret key text loaded from TPM-sealed storage.
"""
msg = "Implement TPM unseal using tpm2-pytss for your platform"
raise NotImplementedError(msg)


@AuthPASETO.load_config
def get_config() -> dict[str, str]:
"""Return runtime configuration for FastAPI PASETO.

This example defaults to an environment variable for simplicity.
"""
return {"authpaseto_secret_key": load_secret_from_env()}
65 changes: 33 additions & 32 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
site_name: FastAPI PASETO
site_description: FastAPI extension that provides PASETO Auth support
theme:
name: material
palette:
primary: black
repo_name: Raze-Systems/fastapi-paseto
repo_url: https://github.com/Raze-Systems/fastapi-paseto
markdown_extensions:
- markdown_include.include:
base_path: docs
- toc:
permalink: true
- pymdownx.highlight:
linenums_style: pymdownx.inline
- pymdownx.magiclink:
user: Raze-Systems
repo: fastapi-paseto
repo_url_shorthand: true
- pymdownx.emoji:
emoji_index: !!python/name:pymdownx.emoji.twemoji
- attr_list
- def_list
- admonition
- codehilite
- pymdownx.tabbed
- pymdownx.superfences
- pymdownx.inlinehilite
theme:
name: material
palette:
primary: black

repo_name: Raze-Systems/fastapi-paseto
repo_url: https://github.com/Raze-Systems/fastapi-paseto

markdown_extensions:
- markdown_include.include:
base_path: docs
- toc:
permalink: true
- pymdownx.highlight:
linenums_style: pymdownx.inline
- pymdownx.magiclink:
user: Raze-Systems
repo: fastapi-paseto
repo_url_shorthand: true
- pymdownx.emoji:
emoji_index: !!python/name:pymdownx.emoji.twemoji
- attr_list
- def_list
- admonition
- codehilite
- pymdownx.tabbed
- pymdownx.superfences
- pymdownx.inlinehilite

nav:
- About: index.md
- Usage:
Expand All @@ -50,13 +50,14 @@ nav:
- Generate Documentation: advanced-usage/generate-docs.md
- Configuration Options:
- General Options: configuration/general.md
- Secret Key Management: configuration/secret-management.md
- HTTP Transport Options: configuration/headers.md
- Denylist Options: configuration/denylist.md
- Examples: examples.md
- API Documentation: api-doc.md
- Supply Chain Security: supply-chain-security.md
- Development - Contributing: contributing.md
- Release Notes: release-notes.md
extra_css:
- 'css/custom.css'

extra_css:
- 'css/custom.css'