Architecture
A tour of the moving parts.
High-level shape
┌───────────────────────────────────────────────────────────────────────┐
│ Your infrastructure │
│ │
│ ┌────────────────────┐ ┌────────────────────────────────┐ │
│ │ Frontend (React) │ ──HTTPS─►│ Backend (Axum app) │ │
│ │ @identsphere/react │ cookies │ ├── your business routes │ │
│ │ ├ AuthProvider │ + Bearer │ └── identsphere-axum routes (mounted) │ │
│ │ ├ useSession() │ └──┬─────────────────────────────┘ │
│ │ ├ useLogin() etc. │ │ │
│ │ └ axios client │ │ SQL │
│ └────────────────────┘ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Postgres │ │ Redis (opt.) │ │
│ │ schema: │ │ session cache│ │
│ │ IdentSphere.* │ └──────────────┘ │
│ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ SMTP/ │ │ S3/R2/MinIO │ │
│ │ Resend/ │ │ avatars │ │
│ │ SES │ └──────────────┘ │
│ └──────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────┘
✗ no calls leave your infra ✗
Crates
| Crate | Purpose |
|---|---|
identsphere-core | Pure domain logic: password hashing, JWT issuance, capability evaluation, audit, RBAC matrix, provider traits (EmailSender, ObjectStorage, SessionCache). No HTTP, no Axum. |
identsphere-axum | HTTP layer. Route handlers, middleware, cookie helpers, multipart handling, AppState. Depends on identsphere-core. |
identsphere-cli | migrate up/down/status, export. Operational tooling. |
identsphere-webhooks | Outbound webhook signing (HMAC-SHA256) + retry policy. |
@identsphere/react (npm) | TypeScript client. React Query hooks, Axios interceptor for refresh + CSRF, route guards, a typed Capability enum mirroring the Rust Permission set. |
Request flow
A typical authenticated request from React frontend to a protected route:
[browser]
│
│ GET /api/projects
│ Cookie: identsphere_at=...; identsphere_csrf=...
│ X-CSRF-Token: ...
▼
[axum router]
│
│ /v1/auth/* routes? → SDK handler
│ other paths → host application
▼
[auth_middleware]
│
│ 1. extract token from cookie or Authorization header
│ 2. validate JWT signature + expiry
│ 3. check session_cache for revocation flag
│ 4. on cache miss, SELECT from user_sessions
│ 5. attach AuthUser to request extensions
│
│ CSRF: state-changing methods require X-CSRF-Token to match cookie
▼
[business handler]
│
│ Extension(auth: AuthUser)
│ → call authorizer.authorize(&auth, &Permission::Foo)
│ → run business logic
│
▼
[Json response]
Authentication ladders
IdentSphere issues sessions at three "Authentication Assurance Levels":
| AAL | Issued by |
|---|---|
| AAL1 | Password login, email OTP, social OAuth |
| AAL2 | MFA challenge, passkey login, password + trusted-browser cookie |
| AAL3 | Reserved for future hardware-attestation flows |
Sensitive endpoints (the step-up routes) require AAL2 within a configurable
TTL window. The middleware reads mfa_verified_at from the access-token
claims AND a cache key keyed on session_family_id — either one being
"fresh" satisfies step-up.
Session lifecycle
register/login ──► user_sessions row created
│
▼
refresh ─────► same row, refresh_token_hash rotated
│
│ ┌────► logout: row.revoked = true
│ │
└──►├────► /sessions/:id DELETE: row.revoked = true
│
└────► /sessions DELETE (all): every row revoked
The session-cache layer stores revocation flags keyed by session_family_id.
Subsequent JWT validation hits the cache first; on cache miss it falls
back to the database.
The AuthUser extension
Every authenticated request carries an AuthUser in Axum's request
extensions:
pub struct AuthUser {
pub user_id: Uuid,
pub organization_id: Uuid,
pub email: Option<String>,
pub is_platform_admin: bool,
pub auth_method: AuthMethod, // Password | PasswordWithMfa | Passkey | ApiKey
pub session_family_id: Option<Uuid>,
pub mfa_verified_at: Option<i64>,
pub scopes: Value,
pub allowed_ips: Value,
pub allowed_referrers: Value,
pub extensions: HashMap<String, Value>,
}
Host applications extract it with Extension(auth): Extension<AuthUser> and
use it directly. The extensions field is a host-controlled escape hatch
for application-specific claims (project_id, billing tier, etc.) — write to
it via an AuthUserExtender implementation.
Data ownership
Every byte of user data lives in your Postgres. IdentSphere has no central data
plane, no telemetry endpoint, no license-check ping. The SDK opens exactly
one outbound network connection: to your email transport, when you send
verification / reset emails. (And those go through your configured
EmailSender trait.)
Multi-instance deployment
The SDK is stateless across processes — there's no in-memory state that must be shared between Axum instances. Run as many replicas as you want behind a load balancer; share the database (and, optionally, Redis).
The one piece of soft state is the session cache. Without Redis, the SDK
falls back to "DB on every request" mode (PostgresOnlyCache) — slower
but correct.