Prefer to keep Veriqa out of your build entirely? It also runs as a separate server your app
talks to over plain OIDC — in the cloud, in Docker or as an OS service — with no Veriqa package
in your dependency tree or SBOM. Veriqa running elsewhere covers what
that changes for a .NET client.
Samples use placeholders (
my-app, bot tokens). Replace them with your own values and never
commit real secrets: keep them in user-secrets in development and a secret store in production.1. Install the packages
2. Register the services
AddVeriqaAuthServer reads configuration from the host’s standard IConfiguration. Inside it you
choose the OpenIddict store, configure the transaction store and add the channels you need.
Program.cs
"Enabled": true) — otherwise only its options and validator are registered.
3. Build the middleware pipeline
Program.cs
MapVeriqaAuthServer maps the Email channel endpoints (magic link and the one-tap email)
automatically — but only when the channel is enabled in configuration
(Veriqa:Channels:Email:Enabled: true). No separate call is needed.wwwroot/locales directory is optional: without it channel messages go out in the base language,
and the startup warning Locale files directory not found … is expected. You do not have to translate
anything by hand: ready-made locales (en, ru, zh, pseudo) ship in the
Veriqa.Core.ChannelLocales package — Localization
explains how to put them to work. Also expected with the default configuration is
Initiator context geolocation is enabled but the GeoIP provider is unavailable: sign-in works, the
geo fields of the initiator context simply stay empty. In Development the startup is also followed
by a few dozen diagnostic info lines about the resolved configuration — that is normal.
4. Configure
Channel secrets and OIDC clients are set in configuration. Everything Veriqa owns lives under the rootVeriqa key: channel sections under Veriqa:Channels, auth-server settings alongside
them in the same root.
appsettings.json
The last two sections are what the user reads when the sign-in ends.
Channels:OutcomeNotice:DisplayIntent says where the outcome receipt appears:
ReplacePrompt — the shipped value, stated here so the setting is visible — puts the receipt in
place of the message that asked the question, and NewMessage leaves the question standing
and adds a message of its own. It is an intent rather than a promise: Telegram and MAX honor it,
WhatsApp always sends a new message, and Email shows no receipt at all.MessageTemplates words those receipts. The product ships its own wordings at
ByType:login:BySurface:…, and a level exhausts every step of the address before the resolution
drops to the level below — so a redeclaration is reached only at the same address, which is
why Templates above sits that deep. {app} is a server slot: for a sign-in it is the
DisplayName of the client. The last step of each ladder uses no slot, because no slot of a
receipt is guaranteed. The full rules are in
Configuration reference.WhatsApp uses the official Meta Cloud API (
Provider: MetaCloudApi); BusinessPhoneNumber
is in E.164 format for the deep link. Wire only the channels you use — a section without
"Enabled": true does not activate the channel, which is why the two sections above are shown
switched off rather than removed. Full Email setup (SMTP for Pull and inbound for Push) is in
Channel setup.redirect_uri is matched against AllowedRedirectUris exactly — wildcards are not supported.
The startup validator requires absolute URIs; use https in production (http only for a
loopback address such as localhost or 127.0.0.1, in development).
5. Wire up the client application
Everything above is the issuer side. The client that signs in against it is an ordinary OIDC relying party: nothing Veriqa-specific goes into it — this is the same wiring you would write for any OpenID Connect provider. In ASP.NET Core that wiring comes from one package:Program.cs (the client application)
Challenge sends an unauthenticated visitor to sign-in — onto the Veriqa page with
the QR code and channel buttons:
Program.cs (the client application)
/signin-oidc with error=access_denied.
AccessDeniedPath is what turns that answer into a redirect to this route — the handler has no
default for it, and without the line the refusal goes to OnRemoteFailure, that is, to the third
route.
The third route takes every other failed return. A sign-in that breaks on the Veriqa side comes
back with error=server_error — see Troubleshooting. AccessDeniedPath
covers access_denied only: for the rest the handler raises OnRemoteFailure, and with no handler
for that event the return ends in an HTTP 500. The page offers to start again and does not show the
error text; the handler writes it to the application log, which names the error code and explains
failures on the client side, while the reason for a server_error is in the Veriqa log and in the
audit trail.
Three values have to agree with the configuration from step 4. Each mismatch has its own symptom,
which is how you tell them apart:
dotnet new web gives every project random ports, so out of the box neither Authority nor the
redirect_uri points at a running process — step 10 starts both on the addresses
used here.
The
my-app client from step 4 is declared without a ClientSecret, which makes it a public
client — and Veriqa requires PKCE (S256) from it. Hence UsePkce = true and no ClientSecret.
For a confidential client the secret is set on both sides.6. How the sign-in flow works
The user starts the transaction on the desktop and confirms it in a trusted channel on their phone. Veriqa tracks the transaction and only then lets OpenIddict issue the result.7. Endpoints
MapVeriqaAuthServer maps the standard OIDC contract and the service endpoints:
The Email channel endpoints are mapped by the same
MapVeriqaAuthServer call automatically —
only when the channel is enabled (Veriqa:Channels:Email:Enabled: true):
8. User claims
The default mapper builds the final claim set from the completed transaction:picture arrives only when all three hold: the client requests the avatar scope
(options.Scope.Add("avatar")), the scope is listed in the client’s AllowedScopes, and
GetClaimsFromUserInfoEndpoint = true — the avatar is never put into the id_token, only userinfo
returns it. To read it without inflating the cookie, see
OIDC + Veriqa explainer.
Override the mapping with your own IClaimsMapper — see Configuration & customization.
9. Channel selection via acr_values
A client can restrict sign-in to a specific channel with the standard OIDCacr_values parameter
in channel:{type} form:
channel:telegram channel:whatsapp) let the user pick from the listed ones.
Without the parameter, all enabled channels are available.
10. Checkpoint
Start both processes on the addresses the configuration expects — the issuer on theAuthority
port, the client on the port of its redirect_uri:
applicationUrl of each project’s Properties/launchSettings.json
works too. The client calls the issuer over https itself, so the ASP.NET Core development
certificate has to exist and be trusted: create it with dotnet dev-certs https, then trust it with
dotnet dev-certs https --trust. On Linux the trust command works as well and prints the
SSL_CERT_DIR value to set; the There was an error trusting the HTTPS developer certificate. line
it ends with is expected there and does not mean the step failed — export the value it printed,
keeping your distribution’s own certificate directory in the list so that other chains (a restore
from nuget.org, any outbound HTTPS call) still verify:
openssl version -d prints OPENSSLDIR — the OpenSSL
configuration directory, not the certificates themselves — and the CA files with the hash links
OpenSSL looks up sit in its certs subdirectory, hence the /certs on the end. On Debian and
Ubuntu that resolves to /usr/lib/ssl/certs; on other distributions the prefix differs, which is
why it is read rather than written out. After that the certificate is trusted. Background is in the
“Trust HTTPS certificate on Linux” section of
Enforce HTTPS in ASP.NET Core.
1
Start the app
Start both processes as shown above and open the client at
https://localhost:7020.2
Trigger the flow
Choose “Sign in with Veriqa” and scan the QR code with a trusted channel on your phone. Email
shows a QR code in Push mode; in Pull mode it shows an address field and a “Get a link” button instead.
3
Confirm on the phone
Confirm the request in Telegram / WhatsApp / other channels / Email. The desktop updates
via SignalR or polling.
4
Confirm on the desktop
With Email — and with any channel when
Veriqa:LoginConfirmation:Mode is OnWebPage — the sign-in
page then asks once more: confirm the sign-in there.5
Inspect the result
Check that you land authenticated and that the expected claims are present.
Veriqa running elsewhere
Everything above is the embedded mode: Veriqa is a set of packages inside your own host. The same server can instead run separately, and your application signs in against it over plain OIDC — Veriqa Cloud, a Docker container of your own, or a systemd unit / Windows service on a machine of your own. For a .NET application that changes the dependency tree, not the code: you keep the client half of step 5 and drop every Veriqa package, so no Veriqa component enters your build or your SBOM.Veriqa Cloud is coming soon, and the cloud row below already describes the contract of its
API. Until then, use self-hosted; details are in Veriqa Cloud.
The wiring is the one from step 5, with a single difference:
the issuer address comes from configuration instead of code, because it is the one value that
changes between the modes and between environments.
Program.cs (the client application)
appsettings.json (the client application)
options.ClientSecret — from user-secrets in development and a secret
store in production, never from appsettings.json. In the cloud the secret is issued by a separate
explicit action on the Applications screen and shown once.
Nothing else on this page becomes mode-specific: the endpoints of step 7, the
claims of step 8 and channel restriction through acr_values
(step 9) are issuer behavior, and the client sees them
through discovery whichever mode it talks to.
How the separate server is delivered is a further choice, and it changes nothing on the client
side: a Docker container — self-hosted quickstart — or an archive
installed as a systemd unit or a Windows service, with no Docker and no .NET runtime on the
machine — Install as an OS service.
Next steps
Channel setup
Telegram, WhatsApp, other channels and Email — end to end.
Configuration reference
Every section, key and default in one place.
Configuration & customization
Sign-in page design, localization, extension points.
Preparing for production
What to check before shipping to production.
OIDC + Veriqa explained
Where Veriqa sits in a standard OIDC flow.
Architecture
The building blocks behind the connector.
Veriqa as a separate server
The same issuer outside your build — in Docker or as an OS service.