Edge overview — Versola Docs
/docs
Pre-1.00.7.5versola.ioGitHub

Edge overview

What edge does in front of your API, which credentials it accepts, how a request moves through it, which ports it serves, and what it does not do.

edge is the traffic plane of Versola: a reverse proxy that signs users in, holds their sessions, checks every request to your API against the permissions and rules stored in central, and then forwards it. It also gives native mobile apps a back channel to auth. Read this page first if you want to put Versola in front of an API; you need a running installation with at least one tenant (Run Versola locally).

When to use edge

Use edge when you want the access decision made once, in front of the API, and not repeated in every service:

  • A browser app with no tokens in JavaScript. edge is the OAuth client. The browser only holds an EDGE_SESSION cookie (HttpOnly, Secure, SameSite=Strict).
  • Policy outside your code. Permissions, per-endpoint CEL rules, step-up authentication and identity injection are configuration in the admin console, not code in the service.
  • A native app under the fapi2 security profile. The app never holds a client credential. See Native apps.
  • Service-to-service calls. A client_credentials token is checked against the client’s permissions with the same pipeline.

Skip edge when your services already validate auth tokens themselves and you do not need a gateway. auth works without it.

Three ways to authenticate a call

edge reads the credential in this order and uses the first one it finds:

OrderCredentialTypical caller
1Authorization: Bearer <access token>Service or script holding an unbound token
2Authorization: DPoP <access token> plus a DPoP proof headerNative app, or any client whose token is bound to a key
3EDGE_SESSION cookie, set by /complete after a browser loginBrowser app

A certificate-bound token from an API client such as a bank arrives as Authorization: Bearer on the mutual-TLS port, not on the plain one. See Calling an API with a certificate-bound token.

The token’s cnf claim, not the scheme the caller chose, decides what must be proven. The rules are listed in Sender-constrained tokens:

  • A token bound to a key (cnf.jkt) is refused over Bearer, with or without a dpop block in the config.
  • A token bound to a certificate (cnf.x5t#S256) is honoured from the cookie, where the certificate is edge’s own, and on the mutual-TLS listener when the caller presented exactly the certificate the token names. Over Bearer on the plain port it is refused: edge cannot see the caller’s certificate there.
  • A token bound to both a key and a certificate is refused on every presentation, including the cookie.
  • A DPoP header on a token that carries no key binding is refused.

When the cookie holds an expired access token, edge refreshes it with the stored refresh token and sends a rotated cookie. A header token is never refreshed.

Request lifecycle

Browser login through a preset

An authorization preset is a named login template on a client: redirect URI, post-login and post-logout URIs, scopes, response type, cookie domain and path. You create presets in the console, under the client.

  1. Your app navigates the browser to GET /login/{presetId}. edge stores a PKCE verifier and state for this login (valid 10 minutes) and answers 303 to auth /authorize. The query parameters acr_values, max_age, prompt, login_hint and ui_locales pass through. return_to, a path of the preset’s own origin such as /resources/auth/settings, is kept by edge and not sent on; it is where step 3 ends instead of the post-login URI. Anything that is not such a path (another host, //host, a backslash, a fragment) is ignored, as is every other parameter.
  2. The user signs in at auth, which redirects to GET /complete on edge with code and state.
  3. edge redeems the code, stores the encrypted refresh token in its own schema, sets EDGE_SESSION and answers 303 to the preset’s post-login URI, or to the return_to path on that URI’s origin. On an error from auth, it redirects to the same place with error, error_description and error_uri appended.
  4. GET /logout/{presetId} sends the browser to auth /logout. auth then calls /logout/frontchannel and /logout/backchannel so edge drops the session.

The cookie value is <presetId>:<accessToken>. Its lifetime is the refresh token’s lifetime, or the access token’s when no refresh token was issued.

Calling the API

Your app calls /resources/{resourceId}/{path} with GET, POST, PUT, PATCH or DELETE. Other methods are not routed. edge then runs a fixed pipeline: token, revocation, binding, resource and endpoint lookup, permission, audience, optional userinfo, the allow rule, step-up, injection, forward. Any step can end the request. The steps, the CEL context and the failure codes are on Endpoint policies.

Forwarding rules:

PartWhat the resource receives
AuthorizationInternal resource: Basic with the resource ID as user and the base64url-encoded resource secret as password. The caller’s token is not forwarded. Public resource: the caller’s token as Bearer
CookieEvery cookie except EDGE_SESSION
Host, DPoP, Content-LengthRemoved
Inject rulesHeader, query and top-level JSON body values written by CEL. They replace what the client sent
ResponseStatus and body as the resource sent them, minus Set-Cookie. edge reads the whole response before sending it

A caller that wants to hide UI controls can ask GET /permissions/me?resource=<id> (repeat the parameter for several resources). It returns the endpoint IDs the caller’s roles allow. The proxy enforces the same rules independently.

What edge does not do

  • It does not terminate TLS on PORT. It serves plain HTTP there; put a TLS-terminating proxy in front. The Secure cookie needs HTTPS. The one TLS listener edge runs itself is the optional mutual-TLS port for /resources.
  • It does not rate-limit, cache, rewrite paths or filter content.
  • It does not stream responses. A large upstream response is buffered in memory first.
  • CEL rules see the token, the userinfo claims, the path parameters, the query, the headers and a JSON body. They cannot call out to another service.
  • Body injection works on a top-level JSON object only, and every injected value is a string.
  • It does not issue tokens or hold user data. auth does.

Ports and operational endpoints

PortVariableDefaultServes
ApplicationPORT8080Everything on this page
DiagnosticsDPORT8081GET /liveness, GET /readiness, GET /metrics
Mutual TLSMPORT8083Only /resources/{resourceId}/{path}, and only when the mutual-tls block exists. Bind address: MBIND_HOST

edge does not use APORT. Without a mutual-tls block it opens no second application port. On a CLI-generated vps deployment edge listens on 8095 and 8096. POST /service/configuration/sync makes edge reload its configuration from central at once. It exists for tests, needs Basic credentials built from security.internal-secret, and answers 404 when env is prod. See Edge configuration and Observability.

Limitations

  • One Postgres for all replicas. Every replica of edge uses the same Postgres: edge’s own database, or the edge schema in a shared one in the simplest setup (the CLI local and vps targets). Sessions, pending logins, revocations and DPoP replay records live there, and replicas coordinate through it.
  • Revocation spreads through Postgres LISTEN/NOTIFY. A revoked token is rejected from an in-memory list that every replica keeps current. LISTEN does not work through a transaction-mode pooler; set postgres.notifications-url to a direct URL (Behind a connection pooler). A reconcile every revocation.reload-interval (default 600 s) is the backstop for a missed notification. A replica that cannot load the list at startup refuses to start.
  • Configuration lags. Resources, endpoints, permissions, clients and presets are cached and reloaded every configuration-cache-refresh-interval. Removing a permission reaches each replica within one interval. Revoking a token does not wait for it.
  • Native apps need the native block, and native needs dpop. Without them every /native/* call answers 404.
  • Certificate-bound tokens need the mutual-tls block, and a native block for fetchUserInfo. An endpoint with fetchUserInfo on and a caller’s certificate-bound token calls auth with edge’s own certificate, which exists only with native. See Calling an API with a certificate-bound token.

Next steps