Skip to main content

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

CratePurpose
identsphere-corePure domain logic: password hashing, JWT issuance, capability evaluation, audit, RBAC matrix, provider traits (EmailSender, ObjectStorage, SessionCache). No HTTP, no Axum.
identsphere-axumHTTP layer. Route handlers, middleware, cookie helpers, multipart handling, AppState. Depends on identsphere-core.
identsphere-climigrate up/down/status, export. Operational tooling.
identsphere-webhooksOutbound 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":

AALIssued by
AAL1Password login, email OTP, social OAuth
AAL2MFA challenge, passkey login, password + trusted-browser cookie
AAL3Reserved 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.