20.8. Challenger Service RESTful API#

The challenger service validates that a user is able to receive challenges at an address (such as e-mail or SMS) and allows an OAuth 2.0 client to obtain access to these validated addresses.

The high-level flow is that an OAuth 2.0 client is first registered with the challenger service (via command-line). Using the command-line tool will print the resulting client ID to the console.

Note

Right now, registration of a unique redirection URI is mandatory for each client. If multiple redirection URIs are needed, it is suggested to just register additional clients. (While OAuth 2.0 would support not registering fixed redirection URIs with a client, this is not recommended as it would create an open redirector.)

Once a client is registered, that client can use the challenger service when it needs a user to prove that the user is able to receive messages at a particular address. However, asking a user to prove access to a particular address can be expensive as it may involve sending an SMS or even postal mail depending on the type of address. Thus, challenger does not allow a user agent to begin an address validation process without prior approval by a registered client. Thus, the process begins with a /setup/$CLIENT_ID request where a client requests challenger to begin an address validation request. The /setup/$CLIENT_ID response contains a nonce which is then used to construct the URL of the endpoint to which the client must redirect the user-agent to begin the address validation and authorization process.

The client then redirects the user-agent to the /authorize/$NONCE endpoint of the challenger service, adding its state, client_id and redirect_uri as query parameters. The redirect_uri must match the redirect URI registered with the client. From this endpoint, the challenger service will return a Web page asking the user to provide its address.

Note

Challenger is a bit unusual in that the $NONCE in the endpoint URL makes the authorization endpoint URL (deliberately) unpredictable, while for many other OAuth 2.0 APIs this endpoint is static. However, this is compliant with OAuth 2.0 as determining the authorization URL is left out of the scope of the standard.

When the user has filled in the form with their address, it will be submitted to the /challenge/$NONCE endpoint and the challenger service will send a challenge to the user’s address and generate an HTML form asking the user to enter the received challenge value.

The user can then enter the answer to the challenge which is then submitted to the /solve/$NONCE endpoint. If the answer is correct, the user agent will be redirected to the client redirect URI that was specified by the OAuth 2.0 client upon /authorize, together with an authorization grant encoded in the redirection URI.

Given this authorization grant, the OAuth 2.0 client can then use the /token endpoint to obtain an access token which will grant it access to the resource.

Using the /info endpoint the client can then finally obtain the (now) verified address of the user.

20.8.1. Version History#

The current protocol version is v9.

  • The Challenger SPA is currently targeting v6.

Version history:

  • v6: add the address_type field to /config

  • v7: adds build_version to /config

  • v8: HTTP status code and error code corrections.

  • v9: adds expires to the /setup/$CLIENT_ID response.

Upcoming versions:

  • None anticipated.

Ideas for future version:

  • vXXX: marker for features not yet targeted for release

20.8.2. Terms of service API#

These APIs allow clients to obtain the terms of service and the privacy policy of a service.

GET /terms#

Get the terms of service of the service. The endpoint will consider the “Accept” and “Accept-Language” and “Accept-Encoding” headers when generating a response. Specifically, it will try to find a response with an acceptable mime-type, then pick the version in the most preferred language of the user, and finally apply compression if that is allowed by the client and deemed beneficial.

The endpoint will set an “Etag”, and subsequent requests of the same client should provide the tag in an “If-None-Match” header to detect if the terms of service have changed. If not, a “304 Not Modified” response will be returned. Note that the “304 Not Modified” will also be returned if the client changed the “Accept-Language” or “Accept-Encoding” header. Thus, if the client would like to download the resource in a different language or format, the “If-None-Match” header must be omitted.

If the “Etag” is missing, the client should not cache the response and instead prompt the user again at the next opportunity. This is usually only the case if the terms of service were not configured correctly.

The “Etag” is generated from the first 256 bits of the SHA-512 hash over the terms and encoded in Crockford base-32. However, this behavior is not normative and clients MUST NOT rely on it.

A “Taler-Terms-Version” header is generated to indicate the legal version of the terms. This header will change whenever something legally changed in the terms of service and the user must review and accept the terms of service again. If the “Taler-Terms-Version” is identical to one that the user has already accepted, there is no need for the user to review the terms again.

When returning a full response (not a “304 Not Modified”), the server should also include a “Avail-Languages” header which includes a comma-separated list of the languages in which the terms of service are available (see availability hints specification). Clients can use this to generate a language switcher for users that may not have expressed a proper language preference.

Response:

200 OK:

The body is the terms of service in the requested encoding and language.

501 Not Implemented:

The exchange lacks a valid terms of service configuration. A human-readable error message is returned. Wallets should not require the human to accept any terms of service (and do not need to show this message).

GET /privacy#

Get the privacy policy of the service. Behaves the same way as the “/terms” endpoint, except that it returns the privacy policy instead of the terms of service.

Response:

200 OK:

The body is the privacy policy in the requested encoding and language.

501 Not Implemented:

The exchange lacks a valid terms of service configuration. A human-readable error message is returned. Wallets should not require the human to accept any terms of service (and do not need to show this message).

20.8.3. Receiving Configuration#

GET /config#

Obtain the key configuration settings of the storage service.

Request:

The request takes no query parameters, headers or body. The response is always application/json; unlike /authorize and /solve, this endpoint does not perform content negotiation on Accept.

Response:

200 OK:

Response is a ChallengerConfigurationResponse. This endpoint has no failure mode of its own: the response is computed once at startup from the configuration, so no error code is ever returned.

405 Method Not Allowed:

The request used a method other than GET, HEAD or OPTIONS. Returned by the request router with an Allow header and an empty body; in particular there is no Taler error code. HEAD is handled exactly like GET but without a response body, as required by RFC 9110 section 9.3.2; since protocol v8.

interface ChallengerConfigurationResponse {
  // Name of the service
  name: "challenger";

  // libtool-style representation of the Challenger protocol version, see
  // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
  // The format is "current:revision:age".
  version: string;

  // Release version of the source code.
  // The format is MAJOR.MINOR.MICOR[-GITDATA]
  // and generally follows the "-v" option of the codebase.
  // Since **v7**.
  build_version: string;

  // URN of the implementation (needed to interpret 'revision' in version).
  // @since v0, may become mandatory in the future.
 implementation?: string;

  // @since **v2**.
  // Object; map of keys (names of the fields of the address
  // to be entered by the user) to objects with a "regex" (string)
  // containing an extended Posix regular expression for allowed
  // address field values, and a "hint"/"hint_i18n" giving a
  // human-readable explanation to display if the value entered
  // by the user does not match the regex. Keys that are not mapped
  // to such an object have no restriction on the value provided by
  // the user.  See "ADDRESS_RESTRICTIONS" in the challenger configuration.
  restrictions: Object;

  // @since **v6**
  // Defines the set of fields asked to the user.
  // The field names are registered via GANA at
  // https://git.taler.net/gana.git/tree/gnu-taler-form-attributes
  // email: CONTACT_EMAIL
  // phone: CONTACT_PHONE
  // postal: CONTACT_NAME, ADDRESS_LINES, ADDRESS_COUNTRY
  // postal-ch: CONTACT_NAME, ADDRESS_LINES
  address_type: "email" | "phone" | "postal" | "postal-ch";

  // Hint to show in the address bar for the user as an example for
  // the format of the address.
  address_hint: string;
}

20.8.4. Setup#

POST /setup/$CLIENT_ID#

This endpoint is used by the client to authorize the execution of an address validation on its behalf. An Authorization header (for now always using a Bearer token) should be included to provide the client’s credentials to authorize access to the challenger service. This token must match the client_secret from the registration of the client with the challenger service (which will also be used in the later /token request).

Request:

The Authorization header is mandatory and must be of the form Bearer secret-token:$CLIENT_SECRET: the value is expected to carry the RFC 8959 secret-token: prefix, and the prefix is part of the secret as stored by challenger-admin. The Bearer scheme token is matched case-insensitively.

The body can be an address in JSON encoding to pre-initialize the address to be used by challenger for this process. If the body is absent, the user will have to enter the full address details. The specific address format depends on the address type. However, ChallengeSetupRequest defines the shared read_only bit that has a special meaning independent of the address type: it informs Challenger that the address should not be editable.

The body is optional: a request with no body at all, or with a body of length zero, is accepted and leaves the address unset. If a body is present it must be a JSON object; any other JSON value is rejected.

Passing an address in the /setup body is supported @since protocol v4.

Response:

200 OK:

Response is a ChallengeSetupResponse.

400 Bad request:

The request is malformed. Error codes used are:

  • TALER_EC_GENERIC_PARAMETER_MALFORMED — the $CLIENT_ID in the URL is not a number (detail is "client_id"), the body is valid JSON but not an object (detail is "address"), or read_only is present in the body but is not a boolean (detail is "read_only").

  • TALER_EC_GENERIC_JSON_INVALID — the body is not well-formed JSON.

403 Forbidden:

The client did not present usable credentials. Since protocol v8; previously reported as 400. In both cases detail is "Authorization". Error codes used are:

  • TALER_EC_GENERIC_PARAMETER_MISSING — there is no Authorization header at all.

  • TALER_EC_GENERIC_PARAMETER_MALFORMED — the header is present but does not use the Bearer scheme, or its value lacks the RFC 8959 secret-token: prefix.

404 Not found:

The challenger service is unaware of a matching client, or the credentials of the client are invalid. These two cases are deliberately not distinguished. Always returned with TALER_EC_CHALLENGER_GENERIC_CLIENT_UNKNOWN.

405 Method Not Allowed:

The request used a method other than POST or OPTIONS. Returned by the request router with an Allow header and an empty body; in particular there is no Taler error code.

500 Internal server error:

The challenger service encountered an internal error. Error codes used are:

  • TALER_EC_GENERIC_DB_STORE_FAILED — the database transaction that authenticates the client and inserts the validation failed, either hard or by exhausting the retries for serialization failures (detail is "do_insert_validation").

  • TALER_EC_GENERIC_PARSER_OUT_OF_MEMORY — the service ran out of memory while buffering the request body.

Details::

interface ChallengeSetupRequest {
  // If true, the given address should not be edited.
  // Defaults to 'false' if not specified.
  read_only?: boolean;

  // Optional, additional fields to pre-populate
  // the address to be validated.
  // The fields depend on the challenger type.
  [x: string]: any;
}
interface ChallengeSetupResponse {
  // Nonce to use when constructing /authorize endpoint.
  nonce: string;

  // Time when this validation process expires.  After this time it can
  // no longer be authorized or exchanged for a token.
  // Available since protocol v9.
  expires: Timestamp;
}

20.8.5. Login#

GET /authorize/$NONCE#
POST /authorize/$NONCE#

This is the “authorization” endpoint of the OAuth 2.0 protocol. This endpoint is used by the user-agent. It will return data to generate a form to enter the address.

The NONCE is a unique value identifying the challenge, should be shown to the user so that they can recognize it when they receive the TAN code.

Note that both for GET and POST requests the request arguments must be given in the URL and the body should be empty. We currently do NOT support using x-www-form-urlencoded arguments in the body, even for a POST.

Request:

Query Parameters:
  • response_type – Must be code

  • client_id – Identifier of the client.

  • redirect_uri – URI-encoded redirection URI to use upon authorization.

  • state – Arbitrary client state to associate with the request.

  • scope – Not supported, any value is accepted.

  • code_challenge – A string to enhance security using PKCE (available since v3).

  • code_challenge_method – The method used for the code_challenge. Options are S256 (SHA-256) or plain (available since v3).

Response:

200 OK:

The the response is a ChallengeStatusResponse. Since protocol v1. The response carries Cache-Control: no-store,no-cache.

302 Found:

Returned when the client explicitly accepts text/html returning a redirection to the WebUI. Since protocol v1. The Location is the relative URL /webui/ followed by the query string of the request with a nonce=$NONCE argument appended. Note that a request without any Accept header, or with Accept: */*, is answered with 200 OK and JSON instead.

400 Bad Request:

The request does not follow the spec. Since protocol v1. Error codes used are:

  • TALER_EC_GENERIC_PARAMETER_MISSINGresponse_type, client_id or (when a code_challenge_method was given) code_challenge is absent; detail names the argument.

  • TALER_EC_GENERIC_PARAMETER_MALFORMED — the $NONCE in the URL is not a valid 52-character Crockford-base32 value (detail is "nonce"; since protocol v8, previously reported as 404), response_type is not code, client_id is not a number, code_challenge_method is neither plain nor S256, a non-web redirect_uri was combined with a plain/absent code_challenge_method (the PKCE downgrade guard), or one of redirect_uri, state, scope and code_challenge is not valid UTF-8 (since protocol v8; previously such a value reached the database and produced a 500); detail names the argument.

404 Not found:

The service is unaware of a matching challenge. Since protocol v1. Returned with TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN when the nonce is well-formed but no matching validation was updated. This deliberately conflates four causes: the nonce is unknown, it has expired, the client_id does not own it, or the redirect_uri does not match the one registered for the client. Distinguishing them would let an unauthenticated caller enumerate validations.

405 Method Not Allowed:

The request used a method other than GET, HEAD, POST or OPTIONS. Returned by the request router with an Allow header and an empty body; in particular there is no Taler error code. HEAD is accepted on every endpoint that accepts GET, and is handled identically but without a response body (RFC 9110 section 9.3.2); since protocol v8.

500 Internal Server Error:

Server is not able to respond due to internal problems. Since protocol v1. Returned with TALER_EC_GENERIC_DB_STORE_FAILED (detail is "update_validation"), both for a hard database error and for a serialization failure that survived all retries.

Note

Unlike RFC 6749 section 4.1.2.1, errors are never reported by redirecting the user-agent back to the redirect_uri with an error argument; all failures above are returned as a JSON body, even when the request asked for text/html.

interface ChallengeStatusResponse {

  // indicates if the given address cannot be changed anymore, the
  // form should be read-only if set to true.
  fix_address: boolean;

  // form values from the previous submission if available, details depend
  // on the ADDRESS_TYPE, should be used to pre-populate the form
  // May contain a boolean field read_only indicating if
  // the client is not allowed to change the address when posting
  // it to the /challenge endpoint.
  // If read_only is present and true, the service forces
  // fix_address to true and changes_left to 0.
  // Omitted entirely (not null) if no address was submitted yet.
  last_address?: Object;

  // is the challenge already solved?
  solved: boolean;

  // number of times the address can still be changed, may or may not be
  // shown to the user
  changes_left: Integer;

  // when we would re-transmit the challenge the next
  // time (at the earliest) if requested by the user;
  // only meaningful if challenge already created
  // @since **v2**
  retransmission_time: Timestamp;

  // how many times might the TAN still be retransmitted
  // @since **v2**
  pin_transmissions_left: Integer;

  // how many times might the user still try entering the TAN code
  // @since **v2**
  auth_attempts_left: Integer;
}

20.8.6. Challenge#

POST /challenge/$NONCE#

This endpoint is used by the user-agent to submit the address to which a challenge should be sent by the challenger service.

Request:

Body should use the mime-type “application/x-www-form-urlencoded”; multipart/form-data is accepted as well. Alternatively, the address may be uploaded directly as a JSON object using the mime-type application/json. In the form encodings, each field name/value pair becomes one string-valued member of the address object, and each field is limited to 1024 bytes. The total request body is limited to 1024 bytes for the form encodings.

The posted form data must contain an address JSON object that follow the restrictions defined in config. If the address provided in the ChallengeSetupRequest of /setup was set to be read_only and that was subsequently returned in the ChallengeStatusResponse, then the body must not change that address. The read_only field itself is ignored when comparing the addresses and is re-inserted by the service, so the client need not (but may) preserve it.

Response:

200 OK:

The response is ChallengeResponse. Since protocol v2. The created variant carries Cache-Control: no-store,no-cache.

400 Bad Request:

The request does not follow the spec. Since protocol v1. Error codes used are:

  • TALER_EC_GENERIC_PARAMETER_MALFORMED — the $NONCE in the URL is not a valid 52-character Crockford-base32 value (detail is "nonce"), the Content-Length header is not a number (detail is "Content-Length"), an application/json body is valid JSON but not an object (detail is "address"), or a submitted field name or value is not valid UTF-8.

  • TALER_EC_CHALLENGER_ADDRESS_RESTRICTION_VIOLATED — a field is absent or violates the regular expression configured for it in restrictions, see config; detail names the offending field, so the user agent can highlight it and show the corresponding hint/hint_i18n. Since protocol v8.

  • TALER_EC_GENERIC_JSON_INVALID — an application/json body is not well-formed JSON.

403 Forbidden:

The address being submitted differs from the previously submitted address but the validation process was set up as read_only and thus the address cannot be changed. Returned with TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_READ_ONLY. Since protocol v4.

404 Not Found:

The service is unaware of a matching challenge. Since protocol v1. Returned with TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN when the nonce is well-formed but unknown, or the validation has expired. These two cases are not distinguished.

405 Method Not Allowed:

The request used a method other than POST or OPTIONS. Returned by the request router with an Allow header and an empty body; in particular there is no Taler error code.

413 Request entity too large:

The request body exceeds the 1024 byte limit. Returned with TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT.

415 Unsupported Media Type:

The Content-Type is missing or is not one the service can parse. Returned with TALER_EC_GENERIC_PARAMETER_MALFORMED and detail set to "Content-Type". Since protocol v8; previously reported as 400.

429 Too Many Requests:

There have been too many attempts to request challenge transmissions for this $NONCE. The user-agent should wait and (eventually) request a fresh nonce to be set up by the client. Since protocol v2. Two distinct situations are distinguished, since the appropriate recovery differs. Since protocol v8:

  • TALER_EC_CHALLENGER_TOO_MANY_ADDRESS_CHANGES — the number of permitted address changes was exhausted. The user must obtain a fresh nonce from the client.

  • TALER_EC_CHALLENGER_TOO_MANY_PIN_TRANSMISSIONS — the number of permitted TAN transmissions for the current address was exhausted. The user may still try a different address if address changes remain.

Note that merely being within the retransmission cooldown is not an error: it is reported as 200 OK with transmitted set to false.

500 Internal Server Error:

Server is not able to respond due to internal problems. Since protocol v1. Error codes used are:

  • TALER_EC_GENERIC_DB_FETCH_FAILED — reading the previously stored address failed (detail is "get_validation_address").

  • TALER_EC_GENERIC_DB_STORE_FAILED — storing the address or confirming the transmitted TAN failed (detail is "do_challenge_address" or "do_challenge_address_confirm_pin").

  • TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE — expanding MESSAGE_TEMPLATE_FILE for the challenge message failed.

  • TALER_EC_GENERIC_PARSER_OUT_OF_MEMORY — the service ran out of memory while buffering an application/json body.

  • TALER_EC_CHALLENGER_ADDRESS_RESTRICTION_MALFORMED — the ADDRESS_RESTRICTIONS configuration for the field named in detail has no regular expression, or one that failed to compile. This is an operator error, not a client error; the request is refused because a restriction that cannot be evaluated must not be treated as “no restriction”. Since protocol v8.

502 Bad Gateway:

The challenger service failed to launch or communicate with its helper process for delivering the challenge (SMS, e-mail, postal mail). Returned with TALER_EC_CHALLENGER_HELPER_EXEC_FAILED. The detail distinguishes the failure: "pipe", "exec", "write", or "$EXIT_CODE/$PROCESS_STATUS" when the helper terminated abnormally or with a non-zero exit code.

// Union discriminated by the "type" field.
type ChallengeResponse = ChallengeRedirect | ChallengeCreateResponse
// @since **v2**
interface ChallengeRedirect {
  // Union discriminator field.
  type: "completed";

  // challenge is completed, use should redirect here
  redirect_url: WebURL;
}
interface ChallengeCreateResponse {
   // Union discriminator field.
   type: "created"

   // how many more attempts are allowed, might be shown to the user,
   // highlighting might be appropriate for low values such as 1 or 2 (the
   // form will never be used if the value is zero)
   attempts_left: Integer;

   // the address that is being validated, might be shown or not
   address: Object;

   // true if we just retransmitted the challenge, false if we sent a
   // challenge recently and thus refused to transmit it again this time;
   // might make a useful hint to the user
   transmitted: boolean;

   // when we would re-transmit the challenge the next
   // time (at the earliest) if requested by the user
   // @since **v2**
   retransmission_time: Timestamp;
 }

20.8.7. Solve#

POST /solve/$NONCE#

Used by the user-agent to submit an answer to the challenge. If the answer is correct, the user will be redirected to the client’s redirect URI, otherwise the user may be given another chance to complete the process.

Request:

Body should use the mime-type “application/x-www-form-urlencoded”; multipart/form-data is accepted as well. The posted form data must contain a “pin” field, whose value must be a decimal unsigned integer. The request body is limited to 1024 bytes.

Response:

200 OK:

If the request ask for application/json the response is a ChallengeSolveResponse. Since protocol v2. Note that this status is only used for the successful outcome; an incorrect or unusable TAN is reported with 403, 409 or 429 (see below).

302 Found:

Only possible if request didn’t ask for application/json. Since protocol v2. The user is redirected to the redirect URI of the client to pass the grant to the client. The target will be the redirect URI specified by the client (during registration and again upon /authorize), plus a code argument with the authorization code, and the state argument from the /authorize endpoint. The state argument is omitted entirely if the client did not supply one. The response body is the plain text Ok!.

400 Bad Request:

The request does not follow the spec. Since protocol v1. Error codes used are:

  • TALER_EC_GENERIC_PARAMETER_MALFORMED — the $NONCE in the URL is not a valid 52-character Crockford-base32 value (detail is "nonce"), the Content-Length header is not a number (detail is "Content-Length"), or the pin field is not a decimal number (detail is "pin").

  • TALER_EC_GENERIC_PARAMETER_MISSING — there is no pin field in the body (detail is "pin").

403 Forbidden:

The TAN was checked and did not match. The response is InvalidPinResponse. Since protocol v1. Returned with TALER_EC_CHALLENGER_INVALID_PIN.

404 Not found:

The service is unaware of a matching challenge, or the validation has expired. Since protocol v1. Returned with TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN.

405 Method Not Allowed:

The request used a method other than POST or OPTIONS. Returned by the request router with an Allow header and an empty body; in particular there is no Taler error code.

409 Conflict:

The service had never actually transmitted a TAN, so solving is naturally impossible. Since protocol v8. The response is an InvalidPinResponse with no_challenge set to true. Returned with TALER_EC_CHALLENGER_NO_CHALLENGE_TRANSMITTED.

413 Request entity too large:

The request body exceeds the 1024 byte limit. Returned with TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT.

415 Unsupported Media Type:

The Content-Type is missing or is not one the service can parse. Returned with TALER_EC_GENERIC_PARAMETER_MALFORMED and detail set to "Content-Type". Since protocol v8; previously reported as 400.

429 Too Many Requests:

There have been too many attempts to solve the challenge for this address (and $NONCE). The user-agent should either try a different address (or wait and (eventually) request a fresh nonce to be set up by the client). Since protocol v2. The body is an InvalidPinResponse in both of the cases below, which are told apart by the error code:

  • TALER_EC_CHALLENGER_NO_PIN_ATTEMPTS_LEFT — the user has run out of TAN guesses but may still request a retransmission or change the address. exhausted is true. Since protocol v8.

  • TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS — the user has exhausted address changes, TAN guesses and retransmissions, so the situation is terminal and all three counters are zero. Since protocol v8 this returns an InvalidPinResponse as well; it previously returned a plain error object with only code, hint and detail. Note that the response consuming the very last guess already reports this, rather than TALER_EC_CHALLENGER_INVALID_PIN with a 403: at that point nothing is left to try, which is the more useful thing to tell the user.

500 Internal Server Error:

Server is not able to respond due to internal problems. Since protocol v1. Returned with TALER_EC_GENERIC_DB_FETCH_FAILED; detail is "do_solve_challenge" when solving failed and "get_validation" when the subsequent construction of the redirect URL failed.

Note

Error responses are always JSON, even when the request asked for text/html; only the success case honours the Accept header by returning a 302 redirect.

Note

Once a challenge has been solved, repeating the request for the same (unexpired) $NONCE succeeds again regardless of the pin submitted, re-issuing the redirect and authorization code.

// Only the "completed" variant occurs with a 200 status; the
// "pending" variant (InvalidPinResponse) is returned with a 403,
// 409 or 429 status.  Since **v8** every unsuccessful /solve uses
// that one shape, so a client need only parse InvalidPinResponse.
type ChallengeSolveResponse = ChallengeRedirect;
interface InvalidPinResponse {
  // Union discriminator field.
  type: "pending";

  // numeric Taler error code, should be shown to indicate the error
  // compactly for reporting to developers
  code: Integer;

  // human-readable Taler error code, should be shown for the user to
  // understand the error
  hint: string;

  // how many times is the user still allowed to change the address;
  // if 0, the user should not be shown a link to jump to the
  // address entry form
  addresses_left: Integer;

  // how many times might the TAN still be retransmitted
  pin_transmissions_left: Integer;

  // how many times might the user still try entering the TAN code
  auth_attempts_left: Integer;

  // if true, the TAN was not even evaluated as the user previously
  // exhausted the number of attempts
  exhausted: boolean;

  // if true, the TAN was not even evaluated as no challenge was ever
  // issued (the user must have skipped the step of providing their
  // address first!)
  no_challenge: boolean;
}

20.8.8. Auth#

POST /token#

This is the token endpoint of the OAuth 2.0 specification. This endpoint is used by the client to provide its authorization code, demonstrating that it has the right to learn a particular user’s validated address. In return, the challenger service returns the access token. Renewal is not supported.

Request:

The request must include an application/x-www-form-urlencoded body (multipart/form-data is accepted as well) specifying the client_id, redirect_uri, client_secret, code and grant_type. The grant_type must be set to authorization_code. The redirect_uri must match the URI from /authorize. The code must be the authorization code that /solve returned to the user. The client_id and client_secret must match the usual client credentials. Since protocol v3, code_verifier can also be included.

The request body is limited to 2048 bytes, as is each individual field. Note that the client credentials must be passed in the body (client_secret_post); HTTP Basic authentication (client_secret_basic) is not supported.

Response:

Error responses follow RFC 6749, section 5.2 with an “error” field in JSON, as well as also returning GNU Taler style error messages. The error values used are invalid_request, unsupported_grant_type, invalid_client, invalid_grant and server_error. Since protocol v8 every error response of this endpoint carries the error field, including 415, 413 and 500.

200 OK:

The body will be a ChallengerAuthResponse.

400 Bad Request:

A required POST field (grant_type, client_id, client_secret, code or redirect_uri) is missing or malformed, grant_type is not authorization_code, or the authorization code was not accepted. Error codes used are:

  • TALER_EC_GENERIC_PARAMETER_MISSING (error is invalid_request, or invalid_grant for a missing code_verifier) — a required field is absent; detail names it.

  • TALER_EC_GENERIC_PARAMETER_MALFORMED (error is invalid_request, or unsupported_grant_type when the grant_type is not authorization_code) — the Content-Length header is not a number, client_id is not a number, or the code_verifier violates the length (43–128) or character-set rules of RFC 7636.

  • TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE (error is invalid_grant) — the code is not well-formed, does not correspond to a solved validation of this client, does not match its recomputed authentication tag, or the code_verifier does not match the stored code_challenge.

  • TALER_EC_CHALLENGER_GRANT_UNKNOWN (error is invalid_grant) — the authorization code was already redeemed, or the validation expired between the two database transactions.

Note

Several of these conditions are answered with a byte-identical response on purpose, so that a caller cannot use /token as an oracle to distinguish “this nonce does not exist” from “this validation has no address” from “your authentication tag is wrong”.

401 Unauthorized:

Authentication of the client failed (per RFC 6749, section 5.2): either the client_id/client_secret pair is invalid, or the redirect_uri does not match the one registered with the client. Error codes used are:

  • TALER_EC_CHALLENGER_CLIENT_AUTHENTICATION_FAILED — the client_id/client_secret pair does not match a registered client. Since protocol v8; previously TALER_EC_CHALLENGER_GENERIC_CLIENT_UNKNOWN, which is now used only by /setup/$CLIENT_ID with a 404 so that each error code maps to exactly one HTTP status.

  • TALER_EC_CHALLENGER_GENERIC_CLIENT_FORBIDDEN_BAD_REDIRECT_URI

Both are returned with error set to invalid_client and a WWW-Authenticate: Bearer error="invalid_client" header. Note that failures concerning the code itself — including PKCE code_verifier mismatches, which exist since protocol v3 — are reported with 400, not 401.

405 Method Not Allowed:

The request used a method other than POST or OPTIONS. Returned by the request router with an Allow header and an empty body; in particular there is no Taler error code.

409 Conflict:

A code was presented for a validation process for which the user has not (yet) submitted any address, so the token cannot be issued. Returned with TALER_EC_CHALLENGER_MISSING_ADDRESS. Removed in v8 (to better match RFC 6749, section 5.2); the condition is now reported as 400 with TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE.

413 Request entity too large:

The request body exceeds the 2048 byte limit. Returned with TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT and error set to invalid_request.

415 Unsupported Media Type:

The Content-Type is missing or is not one the service can parse. Returned with TALER_EC_GENERIC_PARAMETER_MALFORMED, detail set to "Content-Type" and error set to invalid_request. Since protocol v8; previously reported as 400.

500 Internal Server Error:

The challenger service encountered an internal error, for example a database failure or a failure of the SHA-256 or Base64 helpers used for PKCE verification. Error codes used are:

  • TALER_EC_GENERIC_DB_FETCH_FAILEDdetail is "get_client" or "get_validation_pkce".

  • TALER_EC_GENERIC_DB_STORE_FAILEDdetail is "do_insert_token".

  • TALER_EC_GENERIC_DB_SOFT_FAILURE — a serialization failure that survived all retries.

  • TALER_EC_GENERIC_DB_INVARIANT_FAILURE (error is server_error) — the stored validation has a code_challenge but no challenge method.

  • TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE (error is server_error) — the SHA-256 or Base64 helper used for PKCE verification failed.

All of these carry error set to server_error. Note that the stored code_challenge_method being unknown is reported with TALER_EC_GENERIC_DB_INVARIANT_FAILURE since protocol v8; it previously used TALER_EC_GENERIC_PARAMETER_MALFORMED, which wrongly suggested the client’s request was at fault.

Details::

interface ChallengerAuthResponse {
  // Token used to authenticate access in /info.
  access_token: string;

  // Type of the access token.
  token_type: "Bearer";

  // Amount of time that an access token is valid (in seconds).
  expires_in: Integer;

}

20.8.9. Info#

GET /info#

This userinfo endpoint of the OAuth 2.0 specification. This endpoint is used by the client to obtain the user’s validated address.

Request:

Must include the token returned to the client from the /token endpoint as a Bearer token in an Authorization header. The scheme token is matched case-insensitively and may be followed by any amount of whitespace. No query parameters or request body are read.

Response:

Every error response carries an RFC 6749 section 5.2 error field in the JSON body, and every 401 additionally carries the RFC 6750 section 3 challenge:

WWW-Authenticate: Bearer realm="challenger", error="...", error_description="..."

As required by RFC 6750 section 3, the challenge answering a request that carried no authentication credentials at all names only the realm and omits both error and error_description.

200 OK:

The body contains the address as a ChallengerInfoResponse.

401 Unauthorized:

The bearer token is missing, invalid (malformed) or unknown. Error codes used are:

  • TALER_EC_GENERIC_PARAMETER_MISSING — there is no Authorization header (detail is "Authorization"). Since protocol v8 the JSON body and the WWW-Authenticate challenge omit error in this case, per RFC 6750 section 3.

  • TALER_EC_GENERIC_PARAMETER_MALFORMED — the Authorization header does not use the Bearer scheme (error is invalid_request, detail is "Authorization").

  • TALER_EC_CHALLENGER_TOKEN_UNKNOWN — the token does not decode, is unknown, or has expired (error is invalid_token). Since protocol v8; previously TALER_EC_CHALLENGER_GRANT_UNKNOWN, which is now reserved for the authorization grant at /token so that each error code maps to exactly one HTTP status.

Note

A token that does not decode and a token that is simply unknown are answered with a byte-identical response on purpose, so that a caller cannot first learn whether a guessed token is well-formed and only then whether it exists.

404 Not found:

The bearer token is invalid (includes unknown or expired). Returned with TALER_EC_CHALLENGER_GRANT_UNKNOWN. Removed in v8 (to better match RFC 6750); the condition is now reported as 401.

405 Method Not Allowed:

The request used a method other than GET, HEAD or OPTIONS. Returned by the request router with an Allow header and an empty body; in particular there is no Taler error code. HEAD is handled exactly like GET but without a response body, as required by RFC 9110 section 9.3.2; since protocol v8.

500 Internal Server Error:

The challenger service encountered an internal error, typically a database failure. detail is "get_token" and error is server_error. Error codes used are:

  • TALER_EC_GENERIC_DB_FETCH_FAILED — a hard database error.

  • TALER_EC_GENERIC_DB_SOFT_FAILURE — a serialization failure that survived all retries; retrying the request may succeed. Since protocol v8, matching /token; previously this case also used TALER_EC_GENERIC_DB_FETCH_FAILED.

Details::

interface ChallengerInfoResponse {

  // Unique ID of the record within Challenger
  // (identifies the rowid of the token).
  id: Integer;

  // Address that was validated.
  // Key-value pairs, details depend on the
  // address_type.
  address: Object;

 // Type of the address.  This is the address_type of the
 // challenger service as a whole (see /config), not a
 // per-token value.
  address_type: string;

  // How long do we consider the address to be
  // valid for this user.
  expires: Timestamp;

}