16.1.1.3. Validate endpoints¶
The validate REST API verifies one-time passwords, drives challenge- response flows, and supports out-of-band token polling. It is the endpoint group that consumers (RADIUS plugins, SAML adapters, PAM modules, web applications) call to actually authenticate a user.
This is distinct from Authentication endpoints, which issues the JWT-based admin/user session tokens for the management API.
The endpoints fall in five groups:
POST /validate/check— verify a user/serial + password and, for challenge-response tokens, trigger or complete the challenge. Two URL aliases exist for protocol adapters:/validate/radiuscheckshapes the response into RADIUS-friendly status codes (204 / 400), and/validate/samlcheckreturns user attributes alongside the auth result.POST /validate/triggerchallenge— admin-only, requires thetriggerchallengepolicy. Forces a challenge for every matching challenge-response token of a user.GET /validate/polltransaction— anonymous. Out-of-band tokens (push, container) poll this to see whether a challenge has been answered.POST /validate/initialize— anonymous. Bootstraps a FIDO2/passkey challenge before login.POST /validate/offlinerefill— refills the OTP buffer of a token attached to a machine for offline use.
Authentication workflow
In case of authenticating a user:
In case of authenticating a serial number:
See Authentication Modes and Client Modes for the per-token-type interaction model that clients consume from challenge responses.
- POST /validate/offlinerefill¶
Replenish the OTP buffer of a token that is attached to a machine for offline use. Each successful refill rotates the
refilltokenso that the previous one cannot be reused.For HOTP tokens, the response carries enough fresh OTP values to bring the offline buffer back up to the configured count. The caller must supply the last password (PIN+OTP) the end user entered so the server can advance the counter to the right position. For WebAuthn / Passkey tokens, the response carries only the new
refilltokenand the WebAuthn/Passkey machine name is read from the user agent string;passshould be an empty string in that case.The response carries the new
refilltoken, the token serial, and the OTP material underresponse.auth_items.offline. Failures may be masked into a generic error by thehide_specific_error_message_for_offline_refilluser-scope policy.- JSON Parameters:
serial – token serial number (required).
refilltoken – the refill authorization token issued on the previous refill or at offline attachment time (required).
pass – the last PIN+OTP the user entered (required; empty string for WebAuthn / Passkey).
- Status Codes:
200 OK – refill payload in the response body, with the new
refilltokenand OTP material underauth_items.offline.400 Bad Request – the token does not exist, is not marked for offline use, the refilltoken is wrong, or the calling machine cannot be identified from the user agent (WebAuthn/Passkey).
- POST /validate/samlcheck¶
- POST /validate/radiuscheck¶
- POST /validate/check¶
Verify an authentication attempt.
The endpoint is bound to three URL paths that share the same request shape but produce different response shapes for protocol adapters:
/validate/check— standard JSON response,result.valueistrue/false./validate/radiuscheck— RADIUS adapter shape: a successful authentication returns an empty204, a failed authentication an empty400. Error responses (server-side faults) are the same as for/validate/check./validate/samlcheck— SAML adapter shape:result.valueis a dictionary withauth(bool) andattributes(the user’s resolver attributes, optionally extended via the resolver’s attribute map andMULTIVALUEATTRIBUTES).
Either
user(with optionalrealm) orserialis required. The PIN+OTP is sent inpass. Subsequent legs of a challenge-response flow carrytransaction_id(and any additional fields the token type needs). The authorization decision can be vetoed by the AUTHZ-scopeauthorized=deny_accesspolicy (see Authorization policies).- JSON Parameters:
serial – token serial. Either
serialoruseris required.user – login name of the user. Either
serialoruseris required.realm – realm of the user; defaults to the default realm if omitted.
pass – PIN concatenated with OTP. For WebAuthn/Passkey endpoints it may be empty.
type – restrict the authentication to tokens of this type. Requires the AUTHZ policy application_tokentype. Ignored when a serial is supplied.
otponly –
1to skip the PIN check and only verify the OTP value. Used by the management UI; only meaningful withserial.transaction_id – transaction id for the second leg of a challenge-response flow.
state – alias of
transaction_idfor legacy callers.exception –
1to surface delivery failures (SMS, email, push) as HTTP 500 instead of returning a generic challenge-creation error.credential_id – FIDO2 credential id for passkey / WebAuthn authentication.
cancel_enrollment –
1together withtransaction_idcancels an in-progressenroll_via_multichallengeflow without authenticating.
Example Validation Request:
POST /validate/check HTTP/1.1 Host: example.com Accept: application/json user=user realm=realm1 pass=s3cret123456
Example response for a successful authentication:
HTTP/1.1 200 OK Content-Type: application/json { "detail": { "message": "matching 1 tokens", "serial": "PISP0000AB00", "type": "spass" }, "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": true }, "version": "privacyIDEA unknown" }
Example response for this first part of a challenge response authentication:
HTTP/1.1 200 OK Content-Type: application/json { "detail": { "serial": "PIEM0000AB00", "type": "email", "transaction_id": "12345678901234567890", "multi_challenge": [ {"serial": "PIEM0000AB00", "transaction_id": "12345678901234567890", "message": "Please enter otp from your email", "client_mode": "interactive"}, {"serial": "PISM12345678", "transaction_id": "12345678901234567890", "message": "Please enter otp from your SMS", "client_mode": "interactive"} ] }, "id": 2, "jsonrpc": "2.0", "result": { "status": true, "value": false }, "version": "privacyIDEA unknown" }
In this example two challenges are triggered, one with an email and one with an SMS. The application and thus the user has to decide, which one to use. They can use either.
The challenges also contain the information of the “client_mode”. This tells the plugin, whether it should display an input field to ask for the OTP value or e.g. to poll for an answered authentication. Read more at Authentication Modes and Client Modes.
Note
All challenge response tokens have the same
transaction_idin this case.Example response for a successful authentication with
/samlcheck:HTTP/1.1 200 OK Content-Type: application/json { "detail": { "message": "matching 1 tokens", "serial": "PISP0000AB00", "type": "spass" }, "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": {"attributes": { "username": "koelbel", "realm": "themis", "mobile": null, "phone": null, "myOwn": "/data/file/home/koelbel", "resolver": "themis", "surname": "Kölbel", "givenname": "Cornelius", "email": null}, "auth": true} }, "version": "privacyIDEA unknown" }
The response in
value->attributescan contain additional attributes (like “myOwn”) which you can define in the LDAP resolver in the attribute mapping.
- GET /validate/samlcheck¶
- GET /validate/radiuscheck¶
- GET /validate/check¶
Verify an authentication attempt.
The endpoint is bound to three URL paths that share the same request shape but produce different response shapes for protocol adapters:
/validate/check— standard JSON response,result.valueistrue/false./validate/radiuscheck— RADIUS adapter shape: a successful authentication returns an empty204, a failed authentication an empty400. Error responses (server-side faults) are the same as for/validate/check./validate/samlcheck— SAML adapter shape:result.valueis a dictionary withauth(bool) andattributes(the user’s resolver attributes, optionally extended via the resolver’s attribute map andMULTIVALUEATTRIBUTES).
Either
user(with optionalrealm) orserialis required. The PIN+OTP is sent inpass. Subsequent legs of a challenge-response flow carrytransaction_id(and any additional fields the token type needs). The authorization decision can be vetoed by the AUTHZ-scopeauthorized=deny_accesspolicy (see Authorization policies).- JSON Parameters:
serial – token serial. Either
serialoruseris required.user – login name of the user. Either
serialoruseris required.realm – realm of the user; defaults to the default realm if omitted.
pass – PIN concatenated with OTP. For WebAuthn/Passkey endpoints it may be empty.
type – restrict the authentication to tokens of this type. Requires the AUTHZ policy application_tokentype. Ignored when a serial is supplied.
otponly –
1to skip the PIN check and only verify the OTP value. Used by the management UI; only meaningful withserial.transaction_id – transaction id for the second leg of a challenge-response flow.
state – alias of
transaction_idfor legacy callers.exception –
1to surface delivery failures (SMS, email, push) as HTTP 500 instead of returning a generic challenge-creation error.credential_id – FIDO2 credential id for passkey / WebAuthn authentication.
cancel_enrollment –
1together withtransaction_idcancels an in-progressenroll_via_multichallengeflow without authenticating.
Example Validation Request:
POST /validate/check HTTP/1.1 Host: example.com Accept: application/json user=user realm=realm1 pass=s3cret123456
Example response for a successful authentication:
HTTP/1.1 200 OK Content-Type: application/json { "detail": { "message": "matching 1 tokens", "serial": "PISP0000AB00", "type": "spass" }, "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": true }, "version": "privacyIDEA unknown" }
Example response for this first part of a challenge response authentication:
HTTP/1.1 200 OK Content-Type: application/json { "detail": { "serial": "PIEM0000AB00", "type": "email", "transaction_id": "12345678901234567890", "multi_challenge": [ {"serial": "PIEM0000AB00", "transaction_id": "12345678901234567890", "message": "Please enter otp from your email", "client_mode": "interactive"}, {"serial": "PISM12345678", "transaction_id": "12345678901234567890", "message": "Please enter otp from your SMS", "client_mode": "interactive"} ] }, "id": 2, "jsonrpc": "2.0", "result": { "status": true, "value": false }, "version": "privacyIDEA unknown" }
In this example two challenges are triggered, one with an email and one with an SMS. The application and thus the user has to decide, which one to use. They can use either.
The challenges also contain the information of the “client_mode”. This tells the plugin, whether it should display an input field to ask for the OTP value or e.g. to poll for an answered authentication. Read more at Authentication Modes and Client Modes.
Note
All challenge response tokens have the same
transaction_idin this case.Example response for a successful authentication with
/samlcheck:HTTP/1.1 200 OK Content-Type: application/json { "detail": { "message": "matching 1 tokens", "serial": "PISP0000AB00", "type": "spass" }, "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": {"attributes": { "username": "koelbel", "realm": "themis", "mobile": null, "phone": null, "myOwn": "/data/file/home/koelbel", "resolver": "themis", "surname": "Kölbel", "givenname": "Cornelius", "email": null}, "auth": true} }, "version": "privacyIDEA unknown" }
The response in
value->attributescan contain additional attributes (like “myOwn”) which you can define in the LDAP resolver in the attribute mapping.
- POST /validate/triggerchallenge¶
Trigger a fresh challenge for every challenge-response token matching the given user and/or serial. Used by the WebUI and by automation that must initiate challenge-response flows on behalf of a user (for example pre-positioning a push prompt).
Requires admin authentication and the policy action triggerchallenge. The request must carry a valid
PI-Authorizationheader.If the AUTHZ-scope
increase_failcounter_on_challengepolicy is active, the fail counter is incremented on every matching token before the challenges are created.- JSON Parameters:
user – user the challenges should be created for.
realm – realm of the user; defaults to the default realm.
serial – restrict to a specific token.
type – restrict to tokens of this type. Requires the AUTHZ policy application_tokentype. Ignored when
serialis supplied.
- Request Headers:
PI-Authorization – admin auth token.
- Status Codes:
200 OK –
result.valueis the number of created challenges;detail.multi_challengelists them.
Example response for a successful triggering of challenge:
HTTP/1.1 200 OK Content-Type: application/json { "detail": { "client_mode": "interactive", "message": "please enter otp: , please enter otp: ", "messages": [ "please enter otp: ", "please enter otp: " ], "multi_challenge": [ { "client_mode": "interactive", "message": "please enter otp: ", "serial": "TOTP000026CB", "transaction_id": "11451135673179897001", "type": "totp" }, { "client_mode": "interactive", "message": "please enter otp: ", "serial": "OATH0062752C", "transaction_id": "11451135673179897001", "type": "hotp" } ], "serial": "OATH0062752C", "threadid": 140329819764480, "transaction_id": "11451135673179897001", "transaction_ids": [ "11451135673179897001", "11451135673179897001" ], "type": "hotp" }, "id": 2, "jsonrpc": "2.0", "result": { "status": true, "value": 2 }
Example response for response, if the user has no challenge token:
HTTP/1.1 200 OK Content-Type: application/json { "detail": {"messages": [], "threadid": 140031212377856, "transaction_ids": []}, "id": 1, "jsonrpc": "2.0", "result": {"status": true, "value": 0}, "signature": "205530282...54508", "time": 1484303812.346576, "version": "privacyIDEA 2.17", "versionnumber": "2.17" }
Example response for a failed triggering of a challenge. In this case the
statuswill befalse.HTTP/1.1 200 OK Content-Type: application/json { "detail": null, "id": 1, "jsonrpc": "2.0", "result": {"error": {"code": 905, "message": "ERR905: The user can not be found in any resolver in this realm!"}, "status": false}, "signature": "14468...081555", "time": 1484303933.72481, "version": "privacyIDEA 2.17" }
- GET /validate/triggerchallenge¶
Trigger a fresh challenge for every challenge-response token matching the given user and/or serial. Used by the WebUI and by automation that must initiate challenge-response flows on behalf of a user (for example pre-positioning a push prompt).
Requires admin authentication and the policy action triggerchallenge. The request must carry a valid
PI-Authorizationheader.If the AUTHZ-scope
increase_failcounter_on_challengepolicy is active, the fail counter is incremented on every matching token before the challenges are created.- JSON Parameters:
user – user the challenges should be created for.
realm – realm of the user; defaults to the default realm.
serial – restrict to a specific token.
type – restrict to tokens of this type. Requires the AUTHZ policy application_tokentype. Ignored when
serialis supplied.
- Request Headers:
PI-Authorization – admin auth token.
- Status Codes:
200 OK –
result.valueis the number of created challenges;detail.multi_challengelists them.
Example response for a successful triggering of challenge:
HTTP/1.1 200 OK Content-Type: application/json { "detail": { "client_mode": "interactive", "message": "please enter otp: , please enter otp: ", "messages": [ "please enter otp: ", "please enter otp: " ], "multi_challenge": [ { "client_mode": "interactive", "message": "please enter otp: ", "serial": "TOTP000026CB", "transaction_id": "11451135673179897001", "type": "totp" }, { "client_mode": "interactive", "message": "please enter otp: ", "serial": "OATH0062752C", "transaction_id": "11451135673179897001", "type": "hotp" } ], "serial": "OATH0062752C", "threadid": 140329819764480, "transaction_id": "11451135673179897001", "transaction_ids": [ "11451135673179897001", "11451135673179897001" ], "type": "hotp" }, "id": 2, "jsonrpc": "2.0", "result": { "status": true, "value": 2 }
Example response for response, if the user has no challenge token:
HTTP/1.1 200 OK Content-Type: application/json { "detail": {"messages": [], "threadid": 140031212377856, "transaction_ids": []}, "id": 1, "jsonrpc": "2.0", "result": {"status": true, "value": 0}, "signature": "205530282...54508", "time": 1484303812.346576, "version": "privacyIDEA 2.17", "versionnumber": "2.17" }
Example response for a failed triggering of a challenge. In this case the
statuswill befalse.HTTP/1.1 200 OK Content-Type: application/json { "detail": null, "id": 1, "jsonrpc": "2.0", "result": {"error": {"code": 905, "message": "ERR905: The user can not be found in any resolver in this realm!"}, "status": false}, "signature": "14468...081555", "time": 1484303933.72481, "version": "privacyIDEA 2.17" }
- GET /validate/polltransaction/(transaction_id)¶
- GET /validate/polltransaction¶
Report whether a challenge has been answered. Out-of-band tokens (push, container) poll this endpoint to learn when the user has interacted with the challenge so that the calling client can follow up with
POST /validate/check.This endpoint is anonymous — no authentication header is required.
- Parameters:
transaction_id – optional path component, the transaction id to check. May also be supplied as a query parameter.
- Query Parameters:
transaction_id – alternative to the path component.
- Status Codes:
200 OK –
result.valueistrueif at least one non-expired challenge with this transaction id has been answered,falseotherwise.detail.challenge_statusis one ofaccept(an answered challenge exists),declined(the user declined a challenge), orpending(the challenges are still open or no matching challenge exists at all).
- POST /validate/initialize¶
Initialize an authentication by requesting a fresh challenge for a token type. Currently only the
passkeytype is supported; the WebUI calls this to obtain the FIDO2 challenge it then forwards to the browser’snavigator.credentials.getcall.For
passkey, thewebauthn_relying_party_idpolicy must be set (the FIDO2 prepolicy reads it from the AUTH scope); the user-verification requirement is taken from theuser_verification_requirementpolicy and defaults topreferred.This endpoint is anonymous — no authentication header is required.
- JSON Parameters:
type – the token type to initialize a challenge for (
passkey; required).
- Status Codes:
200 OK –
result.valueisfalse(no authentication decision yet);detail.transaction_idcarries the transaction id anddetail.passkeycarries the FIDO2 challenge payload that the client must pass to the authenticator.400 Bad Request – the requested
typeis unsupported, the token type is disabled by policy, or the relying-party id policy is missing.
- GET /validate/initialize¶
Initialize an authentication by requesting a fresh challenge for a token type. Currently only the
passkeytype is supported; the WebUI calls this to obtain the FIDO2 challenge it then forwards to the browser’snavigator.credentials.getcall.For
passkey, thewebauthn_relying_party_idpolicy must be set (the FIDO2 prepolicy reads it from the AUTH scope); the user-verification requirement is taken from theuser_verification_requirementpolicy and defaults topreferred.This endpoint is anonymous — no authentication header is required.
- JSON Parameters:
type – the token type to initialize a challenge for (
passkey; required).
- Status Codes:
200 OK –
result.valueisfalse(no authentication decision yet);detail.transaction_idcarries the transaction id anddetail.passkeycarries the FIDO2 challenge payload that the client must pass to the authenticator.400 Bad Request – the requested
typeis unsupported, the token type is disabled by policy, or the relying-party id policy is missing.