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.
edgeis the OAuth client. The browser only holds anEDGE_SESSIONcookie (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
fapi2security profile. The app never holds a client credential. See Native apps. - Service-to-service calls. A
client_credentialstoken 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:
| Order | Credential | Typical caller |
|---|---|---|
| 1 | Authorization: Bearer <access token> | Service or script holding an unbound token |
| 2 | Authorization: DPoP <access token> plus a DPoP proof header | Native app, or any client whose token is bound to a key |
| 3 | EDGE_SESSION cookie, set by /complete after a browser login | Browser app |
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 overBearer, with or without adpopblock in the config. - A token bound to a certificate (
cnf.x5t#S256) is honoured only from the cookie, because the certificate isedge’s own. - A token bound to both a key and a certificate is refused on every presentation, including the cookie.
- A
DPoPheader 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.
- Your app navigates the browser to
GET /login/{presetId}.edgestores a PKCE verifier andstatefor this login (valid 10 minutes) and answers303toauth/authorize. The query parametersacr_values,max_age,prompt,login_hintandui_localespass through. Everything else is ignored. - The user signs in at
auth, which redirects toGET /completeonedgewithcodeandstate. edgeredeems the code, stores the encrypted refresh token in its own schema, setsEDGE_SESSIONand answers303to the preset’s post-login URI. On an error fromauth, it redirects to the same URI witherror,error_descriptionanderror_uriappended.GET /logout/{presetId}sends the browser toauth/logout.auththen calls/logout/frontchanneland/logout/backchannelsoedgedrops 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:
| Part | What the resource receives |
|---|---|
Authorization | Internal 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 |
Cookie | Every cookie except EDGE_SESSION |
Host, DPoP, Content-Length | Removed |
| Inject rules | Header, query and top-level JSON body values written by CEL. They replace what the client sent |
| Response | Status 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. It serves plain HTTP on
PORT; put a TLS-terminating proxy in front. TheSecurecookie needs HTTPS. - 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
userinfoclaims, 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.
authdoes.
Ports and operational endpoints
| Port | Variable | Default | Serves |
|---|---|---|---|
| Application | PORT | 8080 | Everything on this page |
| Diagnostics | DPORT | 8081 | GET /liveness, GET /readiness, GET /metrics |
edge does not use APORT or MPORT. 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
edgeuses the same Postgres: edge’s own database, or theedgeschema in a shared one in the simplest setup (the CLIlocalandvpstargets). 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.LISTENdoes not work through a transaction-mode pooler; setpostgres.notifications-urlto a direct URL (Behind a connection pooler). A reconcile everyrevocation.reload-interval(default600 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
nativeblock, andnativeneedsdpop. Without them every/native/*call answers404.
Next steps
- Endpoint policies: the CEL rules, step-up and injection per endpoint.
- Edge configuration: every key and secret.
- Protect an API with edge: a working end-to-end setup.
- OpenAPI specifications:
edge.yamlfor the routes above.