π Zero-Trust & Dual JWT
Ferrox adopts a Zero-Trust security posture by default. This means that every single HTTP request, even internal ones coming from other microservices, is treated as hostile until proven valid.
To achieve this at scale without overloading the database, Ferrox replaces traditional session lookups with PASETO v4 Local Tokens (Platform-Agnostic Security Tokens) and a Dual-Token Architecture.
The Dual-Token Architectureβ
When a user logs in, Ferrox issues two tokens:
- Access Token: Short-lived (e.g., 15 minutes). Contains all the necessary identity claims (
user_id,role). - Refresh Token: Long-lived (e.g., 7 days). Stored in an HTTP-Only Secure cookie.
When the Access Token expires, the Frontend Client (generated by the Code Factory) automatically uses the Refresh Token to negotiate a new Access Token.
API Gateway Patternβ
In a Microservice architecture, checking the database to see if a token is valid on every request is an anti-pattern. Ferrox solves this using the Gateway Middleware.
- The API Gateway receives
Authorization: Bearer <token>. - The Gateway verifies the cryptographic signature (symmetric encryption using
Salsa20). This takes microseconds and requires no DB access. - The Gateway extracts the hidden claims (e.g.,
role: Admin,id: UUIDv7). - The Gateway modifies the incoming HTTP request, injecting internal headers:
X-Ferrox-User-Id: UUIDv7. - The request is forwarded to downstream microservices, which now blindly trust the
X-Ferroxheaders because they sit behind the Zero-Trust Gateway.
High-Level Implementationβ
Protecting a route using PASETO is as simple as attaching the paseto_auth middleware to your Axum Router.
use axum::{routing::get, Router, middleware};
use ferrox_security::paseto_auth;
pub fn secure_router() -> Router {
Router::new()
.route("/profile", get(get_profile))
// This middleware intercepts the JWT, validates it, and injects claims
.route_layer(middleware::from_fn(paseto_auth))
}
Public ID vs Internal IDβ
To prevent users from enumerating your database (e.g., /users/1, /users/2), Ferrox strictly forbids exposing auto-incrementing integer IDs to the Frontend.
The JWT payload always encapsulates a PublicId (UUIDv7). The database mapping from PublicId to Internal Primary Key is handled securely in the backend, meaning attackers can never guess the IDs of other users.