Skip to Content
Get StartedArchitecture

Architecture

This page describes how Auris is structured internally and how its components interact. Understanding the architecture helps when debugging integration issues, planning security boundaries, or evaluating Auris for enterprise deployment.


High-Level Overview

Auris is a layered platform. Your application communicates with the Auris API and SDKs. The API layer orchestrates Keycloak for identity operations and adds authorization, security, and developer services on top.

Keycloak handles what it is best at: storing user credentials, managing sessions, federating identity from external providers, and issuing tokens. The Auris API handles everything else: fine-grained authorization, security enforcement, webhooks, audit logging, SCIM provisioning, and the developer-facing API surface.


Tenant Model

Every Auris deployment supports multiple tenants. A tenant is a fully isolated environment with its own:

  • User accounts and credentials
  • Applications (registered OAuth 2.0 clients)
  • Roles, permissions, and authorization policies
  • Security configuration (rate limits, MFA policy, IP rules, CAPTCHA)
  • Email templates, branding, and custom domains
  • Audit logs and webhook endpoints

Under the hood, each tenant maps directly to a Keycloak realm. This means tenant isolation is enforced at the identity store level — users in tenant A cannot authenticate against tenant B’s applications, and their data is never commingled.

The x-tenant header is used on most Auris API calls to identify the active tenant context. SDKs handle this automatically based on the domain (or tenant identifier) you configure at initialization.

The string 'default' is an SDK-level fallback for unscoped local development only. Production deployments must always pass the real Tenant ID. Using 'default' as a hardcoded tenant can silently disable security features like attack protection and MFA enforcement.


Three-Layer Authorization

Auris implements authorization in three layers, each adding more granularity than the previous:

Layer 1 — Keycloak (Authentication + Role Mapping)

Keycloak performs authentication and issues tokens containing a user’s realm roles and application-scoped roles. This is the foundational layer — every authenticated session goes through Keycloak.

Keycloak roles are coarse-grained (e.g., admin, user, viewer). They appear in the JWT realm_access and resource_access claims and are used by Keycloak’s own session management and federation features.

Layer 2 — Prisma RBAC (Fine-Grained Roles with Tri-State Permissions)

Auris maintains its own role and permission store in PostgreSQL (via Prisma). This layer provides:

  • Roles with a named set of permissions
  • Permissions in action:resource format (e.g., manage:users, view:invoices, approve:expenses)
  • Tri-state permission values: ALLOW, DENY, or INHERIT
    • ALLOW — explicitly grants the permission
    • DENY — explicitly revokes it, even if another role grants it (DENY wins)
    • INHERIT — falls back to the parent role or tenant default
  • Per-user permission overrides that can grant or revoke specific permissions regardless of role
  • Application-scoped permissions — the same user can have different permissions in different registered applications

Permission checks happen server-side via POST /api/roles/check. The dashboard enforces permissions on every API route using requirePermission(req, 'action:resource').

Layer 3 — FGA (Fine-Grained Authorization / Zanzibar-style ReBAC)

The FGA layer implements object-level, relationship-based access control, inspired by Google’s Zanzibar paper. It answers questions like “can user Alice read document 42?” or “is user Bob a member of organization X?”.

The FGA model consists of:

  • Authorization Model — a schema (written in OpenFGA DSL) defining object types and the relations between them
  • Relationship Tuples — facts stored in the database (e.g., document:42#viewer@user:alice)
  • Check engine — a recursive Zanzibar algorithm that evaluates whether a subject has a relation to an object, following computed userset and tuple-to-userset rewrites up to a configurable depth (default: 25)

FGA supports six rewrite rule types: this, computedUserset, tupleToUserset, union, intersection, and exclusion. It also supports expand (list all subjects for a relation) and listObjects (list all objects a subject has access to via reverse lookup).

When FGA_ENGINE_ENABLED=true, resource access checks are routed to the FGA engine. The legacy Ory Keto integration remains as a fallback and is deprecated.


Application Types

Auris supports four application types, corresponding to standard OAuth 2.0 client profiles:

TypeDescriptionAuth Flow
WEBBrowser-based apps (SPAs, server-rendered)Authorization Code + PKCE
MOBILENative iOS / Android appsAuthorization Code + PKCE
APIResource servers that validate tokensToken introspection / JWKS
M2MServer-to-server, background jobs, CLI toolsClient Credentials grant

Each application is assigned a Client ID (public) and optionally a Client Secret (for confidential clients). Redirect URIs are registered per application and enforced exactly — no wildcard matching.

For M2M applications, scopes control what the token allows (e.g., read:users, write:invoices). Custom JWT claims can be attached per application to embed additional data into access tokens without an extra API call.


Token Flow

Auris issues three types of tokens following the OpenID Connect specification:

Access Token

  • Format: JWT (JSON Web Token), signed with RS256
  • Lifetime: Short (typically 5–60 minutes, configurable per tenant)
  • Contains: sub (user ID), email, roles, custom claims, aud (application), iss (Auris URL), exp
  • Usage: Sent in the Authorization: Bearer <token> header to protected API endpoints
  • Verification: Validate against the JWKS endpoint at /api/.well-known/jwks.json

Refresh Token

  • Format: Opaque (random string, not a JWT)
  • Lifetime: Long (typically 7–30 days, configurable)
  • Usage: Exchanged for a new access token at POST /api/auth/token (grant type refresh_token) without requiring the user to re-authenticate
  • Security: Single-use rotation — each refresh issues a new refresh token and invalidates the previous one

ID Token

  • Format: JWT, signed with RS256
  • Contains: User identity claims (name, email, picture, phone_number, locale, custom attributes)
  • Usage: Used by the client application to display user information — not sent to APIs
  • Verified: Same JWKS endpoint as the access token

OIDC Discovery

Auris publishes a standard OIDC Discovery document at:

GET /api/.well-known/openid-configuration

This endpoint returns the authorization endpoint, token endpoint, JWKS URI, supported grant types, scopes, and signing algorithms. Standard OIDC clients can auto-configure from this document.

The JWKS endpoint:

GET /api/.well-known/jwks.json

Returns the current public key set in JWK format. Keys use RS256. Key rotation is supported — old keys remain in the JWKS response for a grace period after rotation so in-flight tokens remain valid.


Hosted Login Pages

Auris provides hosted login pages served from the Auris domain (or your custom domain). These pages handle the OAuth 2.0 Authorization Code + PKCE flow end-to-end:

  1. Your application calls loginWithRedirect() (SDK) or redirects to GET /api/oauth/authorize
  2. The user sees the Auris-hosted login page, styled with your tenant’s branding (logo, colors, favicon)
  3. After successful authentication, Auris redirects back to your redirect_uri with an authorization code
  4. Your application exchanges the code for tokens at POST /api/auth/token (handled automatically by SDKs)
  5. The SDK stores the access token and refresh token, and the user is authenticated

All security layers (CAPTCHA, rate limiting, brute-force protection, suspicious login detection, adaptive MFA) are applied during the hosted login flow. You do not need to implement these on your own login pages.


Actions Engine

Actions are custom JavaScript functions that run during authentication flows. They execute in a sandboxed environment with a restricted scope — require, import, process, eval, fs, and child_process are blocked.

Actions can be triggered at six points:

TriggerWhen it fires
pre_loginBefore Keycloak authenticates the user
post_loginAfter successful authentication, before token issuance
pre_signupBefore a new user account is created
post_signupAfter a new user account is created
post_change_passwordAfter a password change
pre_m2m_tokenBefore issuing a machine-to-machine token

Actions have access to a context object containing the user, tenant, application, and event data. They can deny the auth flow, enrich the token with additional claims, or call external services.


Security Architecture

Security is applied in layers, from network to application:

Rate Limiting

Four rate limit tiers with sliding-window counters:

TierApplied toDefault limit
authLogin, signup, password reset10 requests / 15 minutes
sensitive2FA, magic links, phone verify5 requests / hour
apiAuthenticated API calls100 requests / minute
publicUnauthenticated public endpoints300 requests / minute

Rate limit state uses in-memory counters. Standard Retry-After and X-RateLimit-* headers are returned on 429 responses.

Brute Force Protection

Failed login attempts are tracked per account. After a configurable threshold (default: 5 failures in 15 minutes), the account is temporarily locked. Administrators can view and unlock accounts from the console.

CAPTCHA

Three providers are supported: Cloudflare Turnstile, hCaptcha, and Google reCAPTCHA v3. CAPTCHA can be configured to trigger always, only on suspicious requests, or after a number of failed login attempts. CAPTCHA verification is enforced server-side — client-side tokens are validated against the provider’s API before authentication proceeds.

IP Rules

Each tenant can define CIDR-based allow and block rules. Rules can be scoped to the entire tenant or to a specific application. Block rules are evaluated first; if a request IP matches a block rule, it is rejected before any authentication logic runs.

Suspicious Login Detection

After each successful authentication, Auris evaluates five risk signals:

  • New device — the device fingerprint has not been seen before for this user
  • New IP — the IP address has not been used before for this user
  • New country — the geographic country differs from previous sessions
  • Impossible travel — the distance between this login and the previous one is too large for the elapsed time
  • VPN / proxy / datacenter IP — the IP is identified as a VPN exit node, open proxy, or datacenter address

If any signal triggers, a SuspiciousLoginEvent is recorded and an alert notification is sent. Depending on tenant configuration, suspicious logins can be blocked, challenged with step-up MFA, or allowed with logging only.

DPoP Token Binding

For applications requiring the highest level of token security, Auris supports Demonstrating Proof of Possession (DPoP, RFC 9449). DPoP binds access tokens to a client’s public key, preventing stolen tokens from being used by a different client. The dpop-validator middleware validates DPoP proofs on every request for applications with DPoP enabled.


Data Model Summary

The core entities in Auris and their relationships:

All Prisma model changes (new fields or tables) require npx prisma generate and npx prisma db push to take effect. Pre-existing model errors in the networking section resolve automatically after running these commands.