Skip to main content
For non-.NET stacks Veriqa is an ordinary OpenID Connect provider. There is no Veriqa package for Node.js, and none is needed: you write a standard OIDC client, and Veriqa handles all the specifics (sign-in page, QR code, confirmation in the messaging app) on its own side.
One codebase, any deployment. The application never depends on where Veriqa runs — Veriqa Cloud, a Docker container of your own, or a systemd unit / Windows service on a machine of your own. The deployments differ only in the issuer address and in where the client is registered; the code below is identical for all of them, and switching comes down to a single environment variable.

1. Install the packages

The recommended OIDC library is openid-client (OpenID Foundation certified, current line 6.x). It brings neither a web framework nor a session store, and the code below needs both:
express is the framework the routes are written for; express-session is what gives them req.session — where the PKCE code_verifier and the state are kept while the user is away. Without either package, the first run fails on the import line with ERR_MODULE_NOT_FOUND, before the application reaches Veriqa at all.
Node.js 20 or newer is required — that is the minimum declared by the library. The 6.x line is a complete API rewrite: examples written for 5.x (Issuer.discover, client.callback) do not apply to it.
The example is ESM. config.js awaits discovery at the top level of the module, which CommonJS cannot do. Add "type": "module" to your package.json — without it node server.js fails with Cannot use import statement outside a module.

2. Register a client on the Veriqa side

Veriqa Cloud is coming soon, and the cloud rows already describe its API contract. Until then, use self-hosted; details are in Veriqa Cloud.
Self-hosted comes in two delivery options, and this page is the same for both. The server runs in a Docker container — self-hosted quickstart — or is installed from an archive as a systemd unit or a Windows service, with no Docker and no .NET runtime on the machine — Install as an OS service. The choice decides how the server is delivered, not the client registration, the issuer address or any of the code below. Whichever you pick, the client must declare its allowed redirect URIs (matched exactly, wildcards are not supported) and allowed scopes — including channel if the application needs the channel claims (step 7). The redirect_uri is compared literally, so register precisely the URL your callback route serves. For the server of step 6, started as in step 9, that URL is http://127.0.0.1:3000/callback — register exactly that URL, and open the application at the same host (127.0.0.1, not localhost). A secret is not mandatory. The minimal working set is issuer, client_id, redirect_uri: a public client with PKCE, and that is the cloud default. A confidential client is only needed when the application is server-side and you want to authenticate it with a secret; in the cloud the secret is issued by a separate explicit action on the Applications screen and is shown once — store it right away. Keep the values in the environment, not in code. Set them in the shell you will start the application from — the first two are what step 2 gave you, the last two are yours:
SESSION_SECRET is not issued by Veriqa — it is a random string of your own, the key express-session signs the session cookie with. Keep it out of version control and use a separate value in every environment. The public variant needs no VERIQA_CLIENT_SECRET: a public client has no secret at all. If a variable your variant reads is missing, the run fails while config.js is still being imported — new URL(undefined) throws ERR_INVALID_URL — before any route is served. A .env file works just as well if you prefer one: Node.js 20.6 and newer reads it with node --env-file=.env server.js. Switching between deployments — cloud, Docker, OS service — comes down to changing VERIQA_ISSUER alone.
In the cloud the issuer is derived from the project slug. Changing the slug changes the issuer and breaks existing integrations — it is a separate owner action in the Danger zone, not a routine settings edit.

3. Run discovery

The client is built once at process start: the library reads /.well-known/openid-configuration itself and remembers the endpoints.
The first argument is a URL object, not a string. If discovery returns http endpoint addresses while you access it over https, the client is not at fault: the self-hosted server sits behind a proxy with no trusted headers — see step 6 of the self-hosted quickstart.

4. Start the sign-in

PKCE is mandatory and state protects against CSRF. Both values must be stored in the session until the user comes back: they are required in the callback.
login.js
From here Veriqa takes over: the user sees the sign-in page with a QR code and the channel list, confirms the sign-in in a messaging app on their phone, and the browser returns to your redirect_uri with a code.

5. Handle the return

callback.js
authorizationCodeGrant exchanges the code for tokens and validates the response itself; state is checked through expectedState, so you do not verify it again by hand. The claims() method returns the parsed id_token, or undefined if the server issued none. The catch branch handles a sign-in that did not complete. The sign-in page has a Cancel sign-in button, on by default; a user who clicks it comes back to /callback with error=access_denied and no code. authorizationCodeGrant answers an error response by throwing AuthorizationResponseError — without the branch the return ends in an unhandled exception instead of a page of your application. / and /signin-cancelled are where the callback finally lands the browser — the routes step 6 adds.

6. Wire it into a server

The three modules above are handlers, not an application: something has to give them req.session, put them on routes and listen on a port. This is that file, and it is the one you start.
server.js
The route paths are the contract of the page: /callback is what APP_REDIRECT_URI points at and what you registered in step 2, and / shows what the sign-in produced — with nothing in the session yet it sends you to /login, and after a completed sign-in it answers with the stored claims as JSON, so the outcome is visible without a template.

7. Read the claims

Veriqa adds the base set of claims to the tokens itself: Channel claims are issued only with the channel scope: Without the requested channel scope these two claims end up in no token — and therefore do not appear in the userinfo response either, which mirrors the access token. An application uses exactly these claims to tell linked channels apart, so the scope is mandatory for account linking.
The third argument is the expected sub: the library checks it against the response and will not let the subject be swapped.

8. Restrict sign-in to a specific channel

The standard OIDC acr_values parameter in the channel:{type} form narrows the choice on the sign-in page:
Several space-separated values (channel:telegram channel:whatsapp) let the user pick from the listed ones. Without the parameter every channel enabled on the server is available.

9. Checkpoint

Start the application from the directory holding server.js, in the shell where the variables of step 2 are exported:
It serves on http://127.0.0.1:3000 — the address APP_REDIRECT_URI is built from.
1

Check discovery

curl $VERIQA_ISSUER/.well-known/openid-configuration returns metadata with https URLs.
2

Start a sign-in

Open http://127.0.0.1:3000/login — the browser should land on the Veriqa page with the QR code and the channel list.
3

Confirm on the phone

Confirm the request in a trusted channel; the browser returns to /callback, which redirects it to /.
4

Check the claims

/ answers with the session as JSON: sub is filled in, and channel_type carries the channel that confirmed the sign-in — it is absent if the channel scope was not requested.

Next steps

Integration scenarios

Channel linking, re-confirmation and action confirmation — at the HTTP level.

Self-hosted quickstart

Deploy your own issuer in Docker.

Install as an OS service

The same issuer without Docker — systemd or a Windows service, from an archive.

OIDC + Veriqa explained

Where Veriqa fits into the standard flow.

Preparing for production

What to check before shipping.