Skip to main content
For non-.NET stacks Veriqa is an ordinary OpenID Connect provider. There is no Veriqa package for Java, and none is needed: you plug in 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 issuer-uri value.

1. Add the starters

The mainstream path for Java is Spring Security OAuth2 Client through the Spring Boot starter. No version is needed: the Spring Boot BOM manages it. A second starter goes with it — the OAuth2 one declares spring-boot-starter, Spring Security and the OAuth2/OIDC client, and no web server, so on its own the application starts and exits without ever serving the callback:
Together they bring everything required: the embedded Tomcat and Spring MVC the controller of step 5 needs, plus the OAuth2/OIDC client, callback handling and the Spring Security integration.

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. 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 you want to authenticate a server-side application with a secret; in the cloud the secret is issued by a separate explicit action on the Applications screen and is shown once.
The redirect_uri is matched exactly — wildcards are not supported. Spring Security uses the {baseUrl}/login/oauth2/code/{registrationId} template by default, so with registrationId = veriqa and an application at https://app.example.com you must register exactly https://app.example.com/login/oauth2/code/veriqa in Veriqa. This is a common reason a first sign-in is rejected.For the application started as in step 7 that URL is http://127.0.0.1:8080/login/oauth2/code/veriqa — register exactly that URL, and open the application at the same host (127.0.0.1, not localhost): the callback URL is built from the host of the incoming request, and the comparison on the Veriqa side is literal.

3. Describe the client registration

issuer-uri turns on discovery: Spring reads /.well-known/openid-configuration itself and finds the remaining endpoints.
PKCE needs no separate switch. Spring Security applies it automatically when client-authentication-method is none; no extra configuration is necessary for a public client. The secret is then not set at all — by leaving the key out, not by an empty string.
issuer-uri is the address step 2 gave you — replace https://auth.your-domain.com with it, and client-id with the client you registered there. Spring validates the discovery document against this value at startup, so a stale placeholder fails the start rather than the first sign-in. Keep secrets in the environment (${VERIQA_CLIENT_SECRET}), not in a committed application.yml. ${…} is resolved by Spring from the environment of the process, so the confidential variant needs the variable exported in the shell the application is started from:
The public variant reads no secret at all: with client-authentication-method: none the key is absent from application.yml, not empty. If the variable is unset for the confidential one, the start fails on the unresolved placeholder. Switching between deployments — cloud, Docker, OS service — comes down to changing issuer-uri alone.

4. Turn on sign-in

A minimal configuration is enough: oauth2Login() drives the whole flow — the redirect to the Veriqa sign-in page, the callback handling and session creation.
SecurityConfig.java
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. A user who clicks Cancel sign-in on that page comes back with error=access_denied. oauth2Login() needs no code for it: a failed sign-in is redirected to /login?error, the login page Spring Security generates.

5. Read the claims

The authenticated user is available as an OidcUser:
ProfileController.java
This is the route the checkpoint of step 7 opens: it is behind anyRequest().authenticated(), so the first visit starts the sign-in, and the answer after it shows what the sign-in produced. 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.
amr arrives in the id_token as an array of strings — that is how OIDC Core 1.0 §2 defines it, and it stays an array even with a single authentication method. Read it from the ID token and take the first element:
The same method is in the access token as well, so it also shows up in the /connect/userinfo response — which Spring calls when profile is requested (as in the configuration above) and merges with the id_token claims. In userinfo the value is a plain string rather than an array, so user.getClaimAsString("amr") on the merged set depends on which claim won the merge; reading through getIdToken() has no such fork.For linking a channel to an account that is still not enough: amr names the channel type but not the user identifier inside it. The identifier comes from channel_user_id under the channel scope.

6. 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. In Spring Security it is added through an authorization request customizer:
SecurityConfig.java
The resolver is then wired into the chain:
SecurityConfig.java
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.

7. Checkpoint

Start the application from the project directory, in the shell where step 3 exported the secret (the confidential variant only):
Spring Boot serves it on http://127.0.0.1:8080 — the address the redirect_uri registered in step 2 is built from.
1

Check discovery

curl https://auth.your-domain.com/.well-known/openid-configuration — your issuer-uri in place of the host — returns metadata with https URLs.
2

Start a sign-in

Open http://127.0.0.1:8080/ — it is protected by anyRequest().authenticated(), so Spring sends the browser to 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 http://127.0.0.1:8080/login/oauth2/code/veriqa and Spring redirects it back to /.
4

Check the claims

/ answers with the greeting of step 5: OidcUser carries sub, and with the channel scope requested channel_type is available on it.

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.