ZevAuth
Sign up

Your backend

Server SDKs

What every ZevAuth server SDK does, in every language, and the promise that they all behave the same way.

Your server has three jobs with ZevAuth, whatever language it is written in:

  1. Check who is calling. Verify the session token on each request.
  2. Act on users and organizations. Create, update, block, delete, with your secret key.
  3. React to events. Receive webhooks, and check they really came from ZevAuth.

A fourth applies to apps that render pages on the server: see the session on page requests, through the session bridge.

Every ZevAuth server SDK does all four. Pick your language with the Your backend picker in the sidebar.

LanguagePackageFramework integrations
Node.js@zevauth/backendNext.js, React Router, TanStack Start, Astro, Hono, Express
PHPzevauth/zevauth-phpLaravel, any PSR-7 framework (Symfony, Slim, Mezzio), WordPress
PythonzevauthFastAPI, Django, Flask, any framework
RubyzevauthRails, Rack (Sinatra, Roda, Hanami)
Gogithub.com/zevop/zevauth-gonet/http and any router built on it (chi, gorilla/mux)
Java and Kotlincom.zevauth:zevauth-javaSpring Boot (com.zevauth:zevauth-spring-boot-starter), Jakarta Servlet

Until your language’s SDK ships, everything below can be done with any standard JWT library and HTTP client. The tokens are ordinary RS256 JWTs and the API is plain JSON over HTTPS. The sections below give the exact rules.

One contract, every language

The SDKs are not ports of each other. Each one implements the same written contract, and each one runs the same conformance fixtures in its test suite: the same tokens (valid, expired, tampered, signed with the wrong algorithm, signed by an unknown key…), the same webhook signatures and the same API errors, with the same expected answer for every one.

So a token your Node service accepts is accepted by your Python worker, a webhook your PHP app rejects is rejected by your Go service, and an error code means the same thing everywhere.

Checking who is calling

The session token arrives in Authorization: Bearer …. Every SDK verifies it the same way:

  • RS256 only. A token claiming any other algorithm is refused before a key is looked at, including none and the HS256 trick that turns a public key into a password.
  • Your environment’s keys, fetched from /v1/{environmentId}/.well-known/jwks.json, cached for up to 10 minutes and refetched when a token names a key it has not seen (that is what a key rotation looks like), at most once every 30 seconds.
  • Your environment’s issuer (read from the discovery document) and your environment as the audience, so a test token never works on a live route.
  • 5 seconds of clock difference allowed on expiry.

No request is made to ZevAuth per token. The key set is cached, so our availability is never in the path of your requests.

Each SDK offers the same two calls, named the way its language names things:

  • getAuth(request): who is signed in, or nobody. Never fails because a token expired; a page deciding what to render should not crash because a session lapsed.
  • requireAuth(request): the same, but refuses with the response your framework expects.
StatusCodeWhen
401unauthorizedNobody signed in
403session_task_pendingThe person must finish setting up two-step verification first
403mfa_requiredYou asked for two-step verification and this session did not pass it

Both give you the user’s id, the session id, the active organization and role, has({ role, permission }), and whether the session passed a second factor.

Acting on users and organizations

With your secret key (sk_…, never in a browser), every SDK offers users (list, get, create, update, block, sign out everywhere, reset two-step verification, delete) and organizations (list, get, create, update, delete, add and remove members). Lists come back a page at a time, and every SDK can follow the pages for you.

Every SDK retries only what certainly did not happen (a dropped connection, a 429, a 502, 503 or 504), honours the server’s Retry-After, and never retries a 500, because the request may have run. Errors carry the API’s code, the HTTP status, and a request id to quote to ZevAuth support.

Receiving webhooks

Each delivery is signed in the zevauth-signature header, t=<timestamp>,v1=<signature>: an HMAC-SHA256, keyed with your endpoint’s whsec_… secret, over <timestamp>.<raw body>. Every SDK checks it in constant time and refuses deliveries more than 5 minutes old, which stops replays.

Verify the raw body, before parsing it. A body parsed and re-encoded is a different string and fails. Each SDK’s page shows how to get the raw body in its frameworks.

Server-rendered apps

If your server renders pages (Laravel, Rails, Django, Spring MVC, Express templates), the browser requests them itself and sends no Authorization header. The session bridge hands the session to your server in your own cookie, without your server ever holding a refresh token.

Updated at, Sunday, November 1, 2026