Sensitive actions — deleting important data, changing payment details, granting someone access,
approving a payment — deserve a fresh confirmation even from a user who is already signed in.
Veriqa does that with one tap in a messaging app instead of an SMS code.
There are two ways to do it. Signing in again fits when you need to know the same person is still
at the screen. Confirming the action fits when the person must see exactly what they confirm, such
as the amount and recipient of a payment.
What you send
A new authorize request before the action itself, the same one you send for sign-in:
What you check
The auth_time claim in the id_token — the moment the confirmation completed. Freshness is your
call: compare auth_time with the current time and decide whether it fits the window this
operation deserves.
Do not trust your own cookie. The evidence of a fresh confirmation is auth_time from the
new id_token, not the presence of a session in your application: a session only says the
person signed in at some point.
The core does not read max_age. Sending the standard OIDC max_age=0 does no harm, but it
does not affect Veriqa’s behavior — do not build logic on it. There is one freshness mechanism
here: a new authorize plus the auth_time check.
Why this works
There is nothing to bypass: the issuer keeps no long-lived session. The only cookie between
callback and authorize is single-use and is cleared as soon as the code is issued, so every fresh
authorize request asks for confirmation in the channel again.
Going stronger: two different channels
One sensitive operation can be confirmed in two different trusted channels — WhatsApp plus
email, for instance. To narrow a specific request to one channel, use acr_values:
Multiple values are separated by spaces, and the user then picks from the ones listed.
Enriching the profile along the way
A confirmation is a convenient moment to collect what you are missing: request scope=email or
phone and the application receives a verified contact during the confirmation itself — no
separate form, no separate verification. The exact claims depend on the channel.
Confirming a specific action
When the person must see what they confirm, your backend creates a server-to-server transaction:
POST /api/transaction/confirmation with a declared action type and its slot values. The person
gets a message in their messaging app that states the action, such as “Approve a payment of 42.00 EUR
to ACME Ltd?”, and confirms it. To accept a confirmation only from the account owner, the backend
sends expected_identities and checks matched_type in the result. The browser is not involved:
the backend gets a token with client credentials and waits for the transaction outcome.
The samples/dotnet/inproc/step-up sample is ready to run, and the others are on the
Step-up, second factor, human-in-the-loop page. Fields and errors are in
the Server-to-server confirmation API reference.