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 declaresspring-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:
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.
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:
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
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 anOidcUser:
ProfileController.java
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:/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 OIDCacr_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
SecurityConfig.java
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):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.