kych

OAuth 2.0 API for Swiyu to enable Taler integration of Swiyu for KYC (experimental)
Log | Files | Refs | README

api-kych.rst (21849B)


      1 ..
      2   This file is part of GNU TALER.
      3   Copyright (C) 2024, 2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 2.1, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 
     16 .. _kych-api:
     17 
     18 ===============================
     19 KyCH OAuth2 Gateway RESTful API
     20 ===============================
     21 
     22 The KyCH OAuth2 Gateway provides OAuth 2.0 authorization using OID4VP
     23 (OpenID for Verifiable Presentations) with the Swiyu Verifier. It allows
     24 OAuth 2.0 clients (such as a Taler exchange) to verify user identity via
     25 SD-JWT Verifiable Credentials stored in the user's Swiyu Wallet.
     26 
     27 The high-level flow is:
     28 
     29 1. An OAuth 2.0 client is registered with KyCH (via configuration file or CLI tool).
     30 
     31 2. When the client needs to verify a user's identity, it calls ``/setup/$CLIENT_ID``
     32    with its client secret to obtain a ``nonce``.
     33 
     34 3. The client redirects the user-agent to ``/authorize/$NONCE`` with OAuth 2.0
     35    parameters (``response_type``, ``client_id``, ``redirect_uri``, ``state``, ``scope``).
     36 
     37 4. KyCH initiates a verification request with the Swiyu Verifier and returns
     38    a ``verification_url`` (QR code URL) for the user to scan with their Swiyu Wallet.
     39 
     40 5. The user scans the QR code and presents their verifiable credential to the
     41    Swiyu Verifier.
     42 
     43 6. Upon successful verification, Swiyu Verifier sends a webhook notification to
     44    KyCH's ``/notification`` endpoint.
     45 
     46 7. The client polls ``/status/$VERIFICATION_ID`` or the user calls
     47    ``/finalize/$VERIFICATION_ID`` to be redirected back to the client with an
     48    authorization code.
     49 
     50 8. The client exchanges the authorization code for an access token via ``/token``.
     51 
     52 9. The client retrieves the verified identity claims via ``/info``.
     53 
     54 .. contents:: Table of Contents
     55   :local:
     56 
     57 
     58 .. _kych-config:
     59 
     60 -----------------------
     61 Receiving Configuration
     62 -----------------------
     63 
     64 .. http:get:: /config
     65 
     66   Returns the public configuration of the KyCH service, including supported
     67   verifiable credential types and available claims. Clients can use this
     68   endpoint to discover which claims they can request during authorization.
     69 
     70   This endpoint does not require authentication and can be used for health
     71   checks and service discovery.
     72 
     73   **Response:**
     74 
     75   :http:statuscode:`200 OK`:
     76     The service is operational. Returns a `KyCHConfigResponse` containing
     77     the service configuration.
     78 
     79   .. ts:def:: KyCHConfigResponse
     80 
     81     interface KyCHConfigResponse {
     82       // Service identifier. Always "KyCH OAuth2 Gateway".
     83       name: "kych-oauth2-gateway";
     84 
     85       // Semantic version of the KyCH service (e.g., "1.0.0").
     86       version: string;
     87 
     88       // Health status indicator. "healthy" indicates the service is
     89       // ready to accept requests.
     90       status: "healthy";
     91 
     92       // Verifiable credential type identifier configured for this
     93       // KyCH instance. For Swiss Beta ID, this is "betaid-sdjwt".
     94       vc_type: string;
     95 
     96       // Credential format. SD-JWT credentials use "vc+sd-jwt".
     97       vc_format: string;
     98 
     99       // List of cryptographic signature algorithms accepted for
    100       // credential verification (e.g., ["ES256"]).
    101       vc_algorithms: string[];
    102 
    103       // List of claim names that clients can request via the scope
    104       // parameter. Only claims in this list are valid scope values.
    105       // Examples: "family_name", "given_name", "birth_date", "age_over_18".
    106       vc_claims: string[];
    107     }
    108 
    109 
    110 .. _kych-setup:
    111 
    112 -----
    113 Setup
    114 -----
    115 
    116 .. http:post:: /setup/$CLIENT_ID
    117 
    118   Initiates a new KYC verification session for the specified client. This is
    119   the first step in the verification flow and must be called by the client
    120   (e.g., Taler exchange) before redirecting the user to the authorization
    121   endpoint.
    122 
    123   The client authenticates using HTTP Bearer authentication with its client
    124   secret. Upon success, KyCH creates a new session in ``pending`` status and
    125   returns a cryptographically random nonce that identifies this session.
    126 
    127   **Request:**
    128 
    129   :reqheader Authorization: Required. Must be ``Bearer $CLIENT_SECRET`` where
    130     ``$CLIENT_SECRET`` is the secret registered for this client.
    131 
    132   The request body must be empty.
    133 
    134   **Response:**
    135 
    136   :http:statuscode:`200 OK`:
    137     Session created successfully. Returns a `KyCHSetupResponse` containing
    138     the session nonce.
    139 
    140   :http:statuscode:`401 Unauthorized`:
    141     Authentication failed. Either the ``Authorization`` header is missing,
    142     malformed, or contains an invalid client secret. The client should verify
    143     that it is using the correct secret for the specified ``$CLIENT_ID``.
    144 
    145   :http:statuscode:`404 Not found`:
    146     The ``$CLIENT_ID`` in the URL path does not match any registered client.
    147     The client must be registered via configuration file or CLI before use.
    148 
    149   .. ts:def:: KyCHSetupResponse
    150 
    151     interface KyCHSetupResponse {
    152       // Cryptographically random nonce (base64-encoded) that identifies
    153       // this verification session. The client must include this nonce
    154       // in the URL when redirecting the user to /authorize/$NONCE.
    155       // The nonce is single-use and expires after a configured timeout.
    156       nonce: string;
    157     }
    158 
    159 
    160 .. _kych-authorize:
    161 
    162 ---------
    163 Authorize
    164 ---------
    165 
    166 .. http:get:: /authorize/$NONCE
    167 
    168   The OAuth 2.0 authorization endpoint. The client redirects the user's browser
    169   to this endpoint to begin the credential verification process.
    170 
    171   When called, KyCH performs the following steps:
    172 
    173   1. Validates the session identified by ``$NONCE`` exists and is in ``pending`` status.
    174   2. Validates the OAuth 2.0 parameters (response_type, client_id, redirect_uri, scope).
    175   3. Creates a verification request with the configured Swiyu Verifier.
    176   4. Stores the verification details and transitions the session to ``authorized`` status.
    177   5. Returns either an HTML page with a QR code or a JSON response, depending on
    178      the ``Accept`` header.
    179 
    180   The returned ``verification_url`` should be rendered as a QR code for the user
    181   to scan with their Swiyu Wallet. On mobile devices, the ``verification_deeplink``
    182   can be used to launch the wallet app directly.
    183 
    184   **Request:**
    185 
    186   :param NONCE: The session nonce obtained from ``/setup``. This is a URL path
    187     parameter, not a query parameter.
    188 
    189   :query response_type: Required. Must be ``code`` for the authorization code
    190     grant flow. Other values are rejected with ``400 Bad Request``.
    191 
    192   :query client_id: Required. The client identifier. Must match the client that
    193     created this session via ``/setup``. Mismatches are rejected.
    194 
    195   :query redirect_uri: Required. The URI where the user will be redirected after
    196     verification completes. Must exactly match the redirect URI registered for
    197     this client in KyCH's configuration. Mismatches are rejected for security.
    198 
    199   :query state: Required. An opaque value generated by the client for CSRF
    200     protection. KyCH stores this value and includes it in all subsequent
    201     responses and redirects. The client must verify the state matches when
    202     receiving the callback to prevent cross-site request forgery attacks.
    203 
    204   :query scope: Required. Space-delimited list of credential claims to request
    205     from the user's verifiable credential. Each claim must be in the list
    206     returned by ``/config``. Example: ``family_name given_name age_over_18``.
    207     The user will be asked to consent to disclosing these specific claims.
    208 
    209   **Response:**
    210 
    211   :http:statuscode:`200 OK`:
    212     Verification request created successfully. If the ``Accept`` header includes
    213     ``text/html``, returns an HTML page containing a QR code and JavaScript that
    214     polls the status endpoint. Otherwise, returns a `KyCHAuthorizeResponse` JSON
    215     object containing the verification URL.
    216 
    217   :http:statuscode:`400 Bad Request`:
    218     One or more request parameters are invalid:
    219 
    220     - ``response_type`` is not ``code``
    221     - ``redirect_uri`` does not match the registered URI for this client
    222     - ``scope`` contains claims not in the allowed list
    223     - Required parameters are missing
    224 
    225   :http:statuscode:`404 Not found`:
    226     No session exists for the provided ``$NONCE``. The nonce may have never
    227     existed, or it may have already been used (nonces are single-use).
    228 
    229   :http:statuscode:`409 Conflict`:
    230     The session exists but is not in ``pending`` status. This occurs if
    231     ``/authorize`` is called multiple times for the same nonce, or if the
    232     session has already progressed to a later state.
    233 
    234   :http:statuscode:`410 Gone`:
    235     The session has expired. Sessions have a limited lifetime configured on
    236     the server. The client should call ``/setup`` to create a new session.
    237 
    238   :http:statuscode:`502 Bad Gateway`:
    239     KyCH failed to communicate with the Swiyu Verifier when creating the
    240     verification request. This may indicate network issues or verifier
    241     unavailability. The client may retry after a delay.
    242 
    243   .. ts:def:: KyCHAuthorizeResponse
    244 
    245     interface KyCHAuthorizeResponse {
    246       // UUID assigned by the Swiyu Verifier to identify this verification
    247       // request. Used in the /status and /finalize endpoint URLs.
    248       verificationId: string;
    249 
    250       // URL that encodes the OID4VP verification request. This URL should
    251       // be rendered as a QR code for the user to scan with their Swiyu
    252       // Wallet. The wallet will retrieve the presentation request from
    253       // the Swiyu Verifier using this URL.
    254       verification_url: string;
    255 
    256       // Deep link URL for mobile devices. When opened on a device with
    257       // the Swiyu Wallet installed, this launches the wallet app directly
    258       // instead of requiring QR code scanning. Only present if the Swiyu
    259       // Verifier provides it.
    260       verification_deeplink?: string;
    261 
    262       // The state parameter echoed back unchanged. The client should
    263       // verify this matches the state it sent to detect CSRF attacks.
    264       state: string;
    265     }
    266 
    267 
    268 .. _kych-status:
    269 
    270 ------
    271 Status
    272 ------
    273 
    274 .. http:get:: /status/$VERIFICATION_ID
    275 
    276   Polls the current status of a verification request. Clients (or the browser
    277   JavaScript on the authorize page) use this endpoint to detect when the user
    278   has completed the verification process.
    279 
    280   The ``state`` parameter is required and must match the state from the original
    281   authorization request. This prevents unauthorized parties from polling
    282   verification status.
    283 
    284   **Request:**
    285 
    286   :param VERIFICATION_ID: The verification ID returned by ``/authorize``.
    287 
    288   :query state: Required. The state parameter from the original authorization
    289     request. Must match exactly. This provides CSRF protection and prevents
    290     unauthorized status polling.
    291 
    292   **Response:**
    293 
    294   :http:statuscode:`200 OK`:
    295     Returns a `KyCHStatusResponse` containing the current verification status.
    296 
    297   :http:statuscode:`403 Forbidden`:
    298     The ``state`` parameter does not match the state associated with this
    299     verification. This may indicate a CSRF attack or that the wrong state
    300     was provided.
    301 
    302   :http:statuscode:`404 Not found`:
    303     No verification exists with the given ``$VERIFICATION_ID``. The ID may
    304     be invalid or the verification may have been deleted after expiration.
    305 
    306   .. ts:def:: KyCHStatusResponse
    307 
    308     interface KyCHStatusResponse {
    309       // Current status of the verification. Possible values:
    310       //
    311       // - "pending": Session created but /authorize not yet called.
    312       //   (Normally not seen via /status since /authorize must be
    313       //   called first to get the verification_id.)
    314       //
    315       // - "authorized": User has been shown the QR code but has not
    316       //   yet scanned it or completed verification in their wallet.
    317       //   The client should continue polling.
    318       //
    319       // - "verified": The user's credential has been successfully
    320       //   verified by the Swiyu Verifier. The client can now redirect
    321       //   the user to /finalize to obtain the authorization code.
    322       //
    323       // - "failed": Verification failed. This may occur if the user
    324       //   declined consent, presented an invalid credential, or the
    325       //   credential issuer is not trusted.
    326       //
    327       // - "expired": The verification session timed out before the
    328       //   user completed verification. The client should start a
    329       //   new session via /setup.
    330       //
    331       // - "completed": An access token has already been issued for
    332       //   this verification. The flow is complete.
    333       status: "pending" | "authorized" | "verified" | "failed" | "expired" | "completed";
    334     }
    335 
    336 
    337 .. _kych-finalize:
    338 
    339 --------
    340 Finalize
    341 --------
    342 
    343 .. http:get:: /finalize/$VERIFICATION_ID
    344 
    345   Completes the authorization flow by redirecting the user back to the client
    346   with an OAuth 2.0 authorization code. This endpoint should only be called
    347   after polling ``/status`` returns ``verified``.
    348 
    349   When called, KyCH:
    350 
    351   1. Validates the session is in ``verified`` status.
    352   2. Generates a cryptographically random authorization code.
    353   3. Stores the code with a short expiration time (configurable, default 10 minutes).
    354   4. Redirects the user's browser to the client's registered ``redirect_uri``
    355      with the authorization code and state as query parameters.
    356 
    357   The authorization code is single-use. The client must exchange it for an
    358   access token at the ``/token`` endpoint before it expires.
    359 
    360   **Request:**
    361 
    362   :param VERIFICATION_ID: The verification ID returned by ``/authorize``.
    363 
    364   :query state: Required. The state parameter from the original authorization
    365     request. Must match exactly for CSRF protection.
    366 
    367   **Response:**
    368 
    369   :http:statuscode:`302 Found`:
    370     Redirects to the client's ``redirect_uri`` with query parameters:
    371 
    372     - ``code``: The authorization code to exchange for an access token.
    373     - ``state``: The state parameter, echoed back unchanged.
    374 
    375     Example redirect: ``https://client.example.com/callback?code=abc123&state=xyz789``
    376 
    377   :http:statuscode:`400 Bad Request`:
    378     The verification is not in ``verified`` status. This occurs if:
    379 
    380     - The user has not completed verification yet (status is ``authorized``)
    381     - Verification failed (status is ``failed``)
    382     - A token was already issued (status is ``completed``)
    383 
    384   :http:statuscode:`403 Forbidden`:
    385     The ``state`` parameter does not match.
    386 
    387   :http:statuscode:`404 Not found`:
    388     No verification exists with the given ``$VERIFICATION_ID``.
    389 
    390 
    391 .. _kych-token:
    392 
    393 -----
    394 Token
    395 -----
    396 
    397 .. http:post:: /token
    398 
    399   Exchanges an authorization code for an access token. This is the standard
    400   OAuth 2.0 token endpoint implementing the authorization code grant.
    401 
    402   The client authenticates by including its ``client_id`` and ``client_secret``
    403   in the request body (not via HTTP Basic authentication). The ``redirect_uri``
    404   must exactly match the one used in the original authorization request.
    405 
    406   Authorization codes are single-use. Once exchanged for a token, the code
    407   is invalidated and cannot be used again. Codes also expire after a short
    408   time (configurable, default 10 minutes).
    409 
    410   **Request:**
    411 
    412   :reqheader Content-Type: Must be ``application/x-www-form-urlencoded``.
    413 
    414   The request body contains the following form parameters:
    415 
    416   .. ts:def:: KyCHTokenRequest
    417 
    418     interface KyCHTokenRequest {
    419       // The OAuth 2.0 grant type. Must be "authorization_code".
    420       grant_type: "authorization_code";
    421 
    422       // The authorization code received from the /finalize redirect.
    423       // This code is single-use and expires after a short time.
    424       code: string;
    425 
    426       // The client identifier. Must match the client that initiated
    427       // the authorization flow.
    428       client_id: string;
    429 
    430       // The client secret for authentication. KyCH verifies this
    431       // against the stored bcrypt hash.
    432       client_secret: string;
    433 
    434       // The redirect URI. Must exactly match the redirect_uri used
    435       // in the original /authorize request. This prevents authorization
    436       // code injection attacks.
    437       redirect_uri: string;
    438     }
    439 
    440   **Response:**
    441 
    442   :http:statuscode:`200 OK`:
    443     Token issued successfully. Returns a `KyCHTokenResponse` containing
    444     the access token. The session status is updated to ``completed``.
    445 
    446   :http:statuscode:`400 Bad Request`:
    447     The request is invalid. Possible causes:
    448 
    449     - ``grant_type`` is not ``authorization_code``
    450     - ``code`` is missing, invalid, expired, or already used
    451     - ``redirect_uri`` does not match the original request
    452     - Required parameters are missing
    453 
    454   :http:statuscode:`401 Unauthorized`:
    455     Client authentication failed. Either ``client_id`` does not exist or
    456     ``client_secret`` does not match the registered secret.
    457 
    458   .. ts:def:: KyCHTokenResponse
    459 
    460     interface KyCHTokenResponse {
    461       // The access token. Use this as a Bearer token in the
    462       // Authorization header when calling /info.
    463       access_token: string;
    464 
    465       // The token type. Always "Bearer".
    466       token_type: "Bearer";
    467 
    468       // Token validity period in seconds. The token cannot be used
    469       // after this time. Typically 3600 (1 hour).
    470       expires_in: number;
    471     }
    472 
    473 
    474 .. _kych-info:
    475 
    476 ----
    477 Info
    478 ----
    479 
    480 .. http:get:: /info
    481 
    482   Retrieves the verified identity claims using an access token. This endpoint
    483   is analogous to the OAuth 2.0 UserInfo endpoint but returns verified
    484   credential claims instead of user profile information.
    485 
    486   The response contains only the claims that were:
    487 
    488   1. Requested in the ``scope`` parameter during authorization.
    489   2. Successfully disclosed by the user from their verifiable credential.
    490 
    491   The access token is validated on each request. Expired or revoked tokens
    492   are rejected.
    493 
    494   **Request:**
    495 
    496   :reqheader Authorization: Required. Must be ``Bearer $ACCESS_TOKEN`` where
    497     ``$ACCESS_TOKEN`` is the token received from ``/token``.
    498 
    499   **Response:**
    500 
    501   :http:statuscode:`200 OK`:
    502     Returns a `KyCHInfoResponse` containing the verified claims.
    503 
    504   :http:statuscode:`401 Unauthorized`:
    505     The access token is missing, malformed, expired, or has been revoked.
    506     The client should obtain a new token by starting a new verification flow.
    507 
    508   .. ts:def:: KyCHInfoResponse
    509 
    510     interface KyCHInfoResponse {
    511       // The verified credential claims as key-value pairs. Only claims
    512       // that were requested and disclosed are included.
    513       //
    514       // Example response for scope "family_name given_name age_over_18":
    515       // {
    516       //   "family_name": "Muster",
    517       //   "given_name": "Max",
    518       //   "age_over_18": true
    519       // }
    520       //
    521       // Claim types vary:
    522       // - String claims: family_name, given_name, birth_place, nationality, etc.
    523       // - Boolean claims: age_over_16, age_over_18, age_over_65
    524       // - Date claims: birth_date, issuance_date, expiry_date (ISO 8601 format)
    525       // - Binary claims: portrait (base64-encoded image data)
    526       [claim: string]: string | boolean | number | object;
    527     }
    528 
    529 
    530 .. _kych-notification:
    531 
    532 ------------
    533 Notification
    534 ------------
    535 
    536 .. http:post:: /notification
    537 
    538   Webhook endpoint for receiving asynchronous notifications from the Swiyu
    539   Verifier. The verifier calls this endpoint when a verification request
    540   changes state, typically when the user presents their credential.
    541 
    542   When a notification is received, KyCH:
    543 
    544   1. Looks up the verification session by ``verification_id``.
    545   2. Queries the Swiyu Verifier's management API for the verification result.
    546   3. If successful, extracts the disclosed claims and updates the session
    547      to ``verified`` status.
    548   4. If failed, updates the session to ``failed`` status.
    549 
    550   This endpoint always returns ``200 OK`` to acknowledge receipt, regardless
    551   of whether processing succeeded. Errors are logged internally. This prevents
    552   the verifier from retrying notifications that cannot be processed.
    553 
    554   **Request:**
    555 
    556   :reqheader Content-Type: Should be ``application/json``.
    557 
    558   .. ts:def:: KyCHNotificationRequest
    559 
    560     interface KyCHNotificationRequest {
    561       // UUID of the verification request as assigned by the Swiyu
    562       // Verifier. KyCH uses this to look up the corresponding session.
    563       verification_id: string;
    564 
    565       // ISO 8601 timestamp indicating when the verification state
    566       // changed. Used for logging and debugging.
    567       timestamp: string;
    568     }
    569 
    570   **Response:**
    571 
    572   :http:statuscode:`200 OK`:
    573     Notification acknowledged. The response body is empty. This status is
    574     returned regardless of processing outcome to prevent retries.
    575 
    576 
    577 .. _kych-errors:
    578 
    579 ------
    580 Errors
    581 ------
    582 
    583 All error responses use the following format:
    584 
    585 .. ts:def:: KyCHErrorResponse
    586 
    587   interface KyCHErrorResponse {
    588     // Error code identifier
    589     error: string;
    590   }
    591 
    592 Common error codes:
    593 
    594 - ``unauthorized``: Missing or invalid authentication credentials.
    595 - ``invalid_request``: Malformed request parameters.
    596 - ``invalid_redirect_uri``: The redirect URI does not match the registered value.
    597 - ``invalid_scope``: Requested scopes are not allowed.
    598 - ``invalid_grant``: Authorization code is invalid, expired, or already used.
    599 - ``invalid_client``: Client authentication failed.
    600 - ``invalid_token``: Access token is invalid, expired, or revoked.
    601 - ``invalid_state``: The state parameter does not match.
    602 - ``session_not_found``: The session or verification was not found.
    603 - ``session_expired``: The session has expired.
    604 - ``not_verified``: The session has not been verified yet.
    605 - ``verifier_unavailable``: Cannot communicate with the Swiyu Verifier.
    606 - ``verifier_error``: The Swiyu Verifier returned an error.
    607 - ``internal_error``: An internal server error occurred.