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.