Versola is three services, grouped into three planes, plus a proxy that routes traffic and serves the admin console. This page explains what each plane owns and how a request moves through them. It does not cover deployment: see Operations.
The three planes
| Plane | Service | Owns |
|---|---|---|
| Control | central | The source of truth for configuration: tenants, clients, scopes, roles, permissions, resources, signing keys. Exposes the Central API |
| Data | auth | The OAuth 2.0 / OpenID Connect server: login, tokens, sessions, users and their credentials |
| Traffic | edge | An authentication and authorization reverse proxy in front of your APIs. It is the gateway for browser apps (login, sessions) and for native mobile apps |
auth and edge pull their configuration from central. Administrators change configuration only through central, in the admin console or the Central API.
Control plane: central
central stores configuration and serves the Central API (OpenAPI: central.yaml). On an empty database it applies its bootstrap seed once. After that, you change configuration through the console.
The admin console is a static single-page application. It is built into the versola-tools image and served by the proxy at /central/admin/. It has no server of its own: its API calls go to /central/* on the proxy, which routes them through edge to central, so edge sits between the console and central.
Data plane: auth
auth runs the OAuth 2.0 and OIDC flows: /authorize, /par, /token, /introspect, /revoke, /userinfo, logout and discovery. It runs the sign-in conversation (password, OTP, passkey), issues and signs tokens, and stores users. Optionally it runs a second TLS listener for mutual TLS (see sender-constrained tokens).
Traffic plane: edge
edge has three jobs:
- Browser sessions. It acts as the OAuth client for web apps (
/login/{presetId},/complete). Tokens stay on the server side of the exchange, and the browser holds anEDGE_SESSIONcookie (HttpOnly,Secure,SameSite=Strict). Refresh tokens are kept in theedgeschema, andedgerefreshes expired access tokens. - Policy proxy. It proxies
/resources/{resourceId}/{path}to your upstream API. For each request it checks revocation, token binding, permissions, audience and the endpoint’s CEL rules, then injects headers or query values built from the token and user. See Edge. - Native app gateway. Mobile apps call
/native/*endpoints, andedgeruns the authorization on their behalf using PAR and a device-held DPoP key. See Native apps.
Databases
Each service owns its data and its migration history, and the services talk to each other only over HTTP APIs. In the standard topology every service has its own Postgres database, so they scale, back up and fail independently. The smallest deployment puts all three in one database, each in its own schema (auth, central, edge). That is what versola bootstrap local and the VPS deployment set up. A service never reads another service’s tables in either topology.
flowchart TB
admin[["👤 Administrator"]]
user[["👤 End user"]]
subgraph versola["⚡ Versola"]
proxy["versola-proxy<br/><i>nginx</i><br/>Routing and admin console files"]
edge["edge<br/><i>traffic plane</i><br/>Sessions, policies, native gateway"]
auth["auth<br/><i>data plane</i><br/>OAuth 2.1 and OIDC server"]
central["central<br/><i>control plane</i><br/>Configuration and Central API"]
db[("Postgres<br/>one database<br/>schemas auth, central, edge")]
end
upstream{{"🔌 Upstream APIs"}}
notifications{{"📧 OTP delivery"}}
admin -->|Console and API| proxy
user -->|Login and API calls| proxy
proxy --> edge
proxy --> auth
edge -->|Token, PAR, userinfo| auth
edge -->|Console API calls| central
edge -.->|Sync| central
auth -.->|Sync| central
edge -->|Proxy| upstream
auth -->|Request OTP| notifications
central --> db
auth --> db
edge --> db
style admin fill:#92400e,stroke:#92400e,color:#fff
style user fill:#166534,stroke:#166534,color:#fff
style proxy fill:#334155,stroke:#e6edf3,color:#fff
style central fill:#155e75,stroke:#e6edf3,color:#fff
style edge fill:#155e75,stroke:#e6edf3,color:#fff
style auth fill:#155e75,stroke:#e6edf3,color:#fff
style db fill:#0e4a5c,stroke:#e6edf3,color:#fff
style upstream fill:#334155,stroke:#e6edf3,color:#fff
style notifications fill:#334155,stroke:#e6edf3,color:#fff
style versola fill:#eaf3f5,stroke:#155e75,stroke-width:2px,color:#15171c
Ports
| Port | Variable | Serves |
|---|---|---|
8080 | PORT | The service API (auth) |
8081 | DPORT | /metrics, /liveness, /readiness |
8082 | APORT | An internal API surface (auth: account settings) |
8083 | MPORT | Mutual TLS listener of auth, and of edge for /resources, each only when its mutual-tls block is configured |
On a CLI-generated server (vps) deployment, central listens on 8090 (diagnostics 8091), edge on 8095 (diagnostics 8096) and versola-proxy on 2821. The local stack also publishes the proxy on 127.0.0.1:2821.
How a request moves
flowchart TB
admin[["👤 Admin<br/><i>Manages platform</i>"]]
user[["👤 End User<br/><i>Accesses apps</i>"]]
versola["⚡ Versola<br/><b>Identity & Authorization Platform</b>"]
upstream{{"🔌 Upstream<br/>Backend APIs"}}
notifications{{"📧 Notification Channel<br/>Notifications"}}
admin -->|Manage| versola
user -->|Auth| versola
versola -->|Proxy| upstream
versola -->|Request Notification| notifications
style admin fill:#92400e,stroke:#92400e,color:#fff
style user fill:#166534,stroke:#166534,color:#fff
style versola fill:#155e75,stroke:#e6edf3,color:#fff,stroke-width:3px
style upstream fill:#334155,stroke:#e6edf3,color:#fff
style notifications fill:#334155,stroke:#e6edf3,color:#fff
Browser app
- The browser opens
GET /login/{presetId}onedge. edgepushes the authorization request toauthwhen the client requires PAR, and redirects the browser toauth/authorizewith PKCE andstate.authsigns the user in and redirects back toedge/completewith a code.edgeredeems the code at/token, stores the refresh token and sets theEDGE_SESSIONcookie.- Each API call carries the cookie.
edgevalidates the token, evaluates the policy and proxies the request.
sequenceDiagram
autonumber
participant B as Browser
participant E as edge
participant A as auth
participant U as Upstream
B->>E: GET /login/:presetId
E->>A: POST /par (when the client requires PAR)
E-->>B: 302 → auth /authorize (PKCE, state)
note over B,A: Challenge flow (password / OTP / passkey)
A-->>B: 302 → edge /complete?code=…&state=…
B->>E: GET /complete?code=…
E->>A: POST /token (code + PKCE verifier)
A-->>E: access_token + refresh_token
E->>E: Store refresh token in DB
E-->>B: 302 → app + Set-Cookie: EDGE_SESSION
note over B,E: Session active — every request carries the cookie
B->>E: GET /resources/api/v1/data
E->>E: Validate token, check revocation, evaluate CEL rules
E->>U: Forward + inject Authorization header (Basic / Bearer)
U-->>E: 200 OK
E-->>B: 200 OK (upstream Set-Cookie stripped)
Mobile app through edge
A native client fronted by edge never holds a client credential. edge authenticates to auth as the client with a certificate on the mTLS listener, and the device holds a DPoP key that binds the tokens. The redirect is an App Link, not a custom scheme.
sequenceDiagram
autonumber
participant M as Mobile app
participant E as edge
participant A as auth
participant U as Upstream
M->>E: POST /native/start/{clientId} + DPoP proof (device key)
E->>A: POST /par as the client (PKCE, state, dpop_jkt)
E-->>M: request_uri, sealed blob
note over M,A: System browser opens auth /authorize, user signs in
A-->>M: Redirect to the App Link with code and state
M->>E: POST /native/complete/{clientId} + blob + DPoP proof
E->>A: POST /token (code + PKCE verifier from blob)
A-->>E: DPoP-bound access_token + refresh_token
E-->>M: Token response relayed unchanged
M->>M: Store tokens and device key (Keychain / Keystore)
note over M,E: Session active — app sends DPoP-bound calls
M->>E: GET /resources/api/v1/data + Authorization: DPoP + DPoP proof
E->>E: Check revocation, token binding, permissions and CEL rules
E->>U: Forward + inject Authorization header
U-->>E: 200 OK
E-->>M: 200 OK
note over M,E: Refresh
M->>E: POST /native/token/{clientId} (refresh_token) + DPoP proof
E->>A: POST /token as the client
A-->>E: new tokens
E-->>M: new tokens
A native app can also be a public client that talks to auth directly, but only on a tenant with the standard security profile.