From ef9d6c5fc4139137ee1c49337fc96d86a2ee601a Mon Sep 17 00:00:00 2001 From: Jiyoon Koo Date: Tue, 16 Jan 2024 10:51:24 -0500 Subject: [PATCH] Update readme to detail JWT token requirement for get exchanges endpoint auth (#216) * adding authn and authz sections where missing. added authn and authz section for list exchanges to expand on JWT token required fields * fixing readme links * Apply suggestions from code review Co-authored-by: Andres Uribe * Update specs/http-api/README.md Co-authored-by: Andres Uribe * moving the auth wordings to the top of the readme * referencing rfc6750 spec in describing jwt bearer token --------- Co-authored-by: Andres Uribe --- specs/http-api/README.md | 194 +++++++++++++++++++++++++++------------ 1 file changed, 137 insertions(+), 57 deletions(-) diff --git a/specs/http-api/README.md b/specs/http-api/README.md index 1cb11176..bc6c64e3 100644 --- a/specs/http-api/README.md +++ b/specs/http-api/README.md @@ -14,51 +14,63 @@ Version: Draft - [Discoverability](#discoverability) - [Example](#example) - [Error Responses](#error-responses) + - [Error object](#error-object) + - [Example](#example-1) - [Query Params](#query-params) - [Pagination](#pagination) - - [Example](#example-1) + - [Example](#example-2) - [Idempotency](#idempotency) +- [Protected Endpoints](#protected-endpoints) + - [Authentication](#authentication) + - [Token Generation](#token-generation) + - [Header](#header) + - [Claims Set](#claims-set) + - [Token Verification](#token-verification) - [Offerings](#offerings) - [List Offerings](#list-offerings) - [Description](#description) - [Endpoint](#endpoint) - - [Authentication](#authentication) - - [Authorization](#authorization) + - [Protected](#protected) - [Query Params](#query-params-1) - [Response](#response) - [Exchanges](#exchanges) - [Submit RFQ](#submit-rfq) + - [Description](#description-1) - [Endpoint](#endpoint-1) - - [Authentication](#authentication-1) - - [Authorization](#authorization-1) + - [Protected](#protected-1) - [Request Body](#request-body) - [Response](#response-1) - [Errors](#errors) - - [Get Quote](#get-quote) + - [Get Quote/Order/OrderStatus](#get-quoteorderorderstatus) + - [Description](#description-2) - [Endpoint](#endpoint-2) + - [Protected](#protected-2) - [Response](#response-2) - [Response Body](#response-body) - [Submit Order](#submit-order) + - [Description](#description-3) - [Endpoint](#endpoint-3) + - [Protected](#protected-3) - [Order Request Body](#order-request-body) - [Response](#response-3) - [Errors](#errors-1) - [Submit Close](#submit-close) - - [Description](#description-1) + - [Description](#description-4) - [Endpoint](#endpoint-4) + - [Protected](#protected-4) - [Request Body](#request-body-1) - [Response](#response-4) - [Errors](#errors-2) - [Get Exchange (DESCOPED)](#get-exchange-descoped) - - [Description](#description-2) - - [Authentication](#authentication-2) + - [Description](#description-5) - [Endpoint](#endpoint-5) + - [Protected](#protected-5) - [Query Params](#query-params-2) - [Response](#response-5) - [List Exchanges](#list-exchanges) - - [Description](#description-3) - - [Authentication](#authentication-3) + - [Description](#description-6) - [Endpoint](#endpoint-6) + - [Protected](#protected-6) - [Response](#response-6) - [Query Params](#query-params-3) - [References](#references) @@ -94,24 +106,24 @@ If the serviceEndpoint is itself a DID, this did should resolve to a document an * If present, the body of an error response will conform to the following: -| Field | Required (Y/N) | Description | -| ------------------ | -------------- | ------------------------- | -| `errors` | Y | List of `Error` objects | +| Field | Required (Y/N) | Description | +| -------- | -------------- | ----------------------- | +| `errors` | Y | List of `Error` objects | ## Error object | Field | Required (Y/N) | Description | | ------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `id` | N | A unique identifier for this particular occurrence of the problem. | -| `status` | N | The HTTP status code applicable to this problem, expressed as a string value. This SHOULD be provided. | -| `code` | N | An application-specific error code, expressed as a string value. | -| `title` | N | A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization. | -| `detail` | Y | A human-readable explanation specific to this occurrence of the problem. Like `title`, this field’s value can be localized. | -| `source` | N | An object containing references to the primary source of the error. It should include the `pointer`, `parameter`, or `header` members or be omitted. | -| `source.pointer` | N | A JSON Pointer to the value in the request document that caused the error. This MUST point to a value in the request document that exists; if it doesn’t, the client SHOULD simply ignore the pointer. | -| `source.parameter` | N | A string indicating which URI query parameter caused the error. | -| `source.header` | N | A string indicating the name of a single request header which caused the error. | -| `meta` | N | A meta object containing non-standard meta-information about the error. | +| `id` | N | A unique identifier for this particular occurrence of the problem. | +| `status` | N | The HTTP status code applicable to this problem, expressed as a string value. This SHOULD be provided. | +| `code` | N | An application-specific error code, expressed as a string value. | +| `title` | N | A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization. | +| `detail` | Y | A human-readable explanation specific to this occurrence of the problem. Like `title`, this field’s value can be localized. | +| `source` | N | An object containing references to the primary source of the error. It should include the `pointer`, `parameter`, or `header` members or be omitted. | +| `source.pointer` | N | A JSON Pointer to the value in the request document that caused the error. This MUST point to a value in the request document that exists; if it doesn’t, the client SHOULD simply ignore the pointer. | +| `source.parameter` | N | A string indicating which URI query parameter caused the error. | +| `source.header` | N | A string indicating the name of a single request header which caused the error. | +| `meta` | N | A meta object containing non-standard meta-information about the error. | --- @@ -156,9 +168,62 @@ The IDs of individual tbDEX messages are used as idempotency keys --- +# Protected Endpoints + +A **_protected endpoint_** is defined as one that requires the requester to be _authenticated_. These endpoints respond with resources specific to the authenticated DID e.g. +* Messages sent by or intended for the requester only +* Balances + +> [!NOTE] +> Each individual endpoint section in this specification will include `Protected: true/false` to indicate whether it is protected + +## Authentication + +When accessing a protected endpoint, a tbdex http client MUST set the HTTP [Authorization Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) in order to be authenticated. + +The value of the header MUST use the Bearer Authentication Scheme as defined in [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750#section-2.1), and the access token MUST be a JWT. + +### Token Generation + +The bearer token is a [JSON Web Token (JWT)](https://datatracker.ietf.org/doc/html/rfc7519) generated by the requester. It must consist of JWT header and Claims Set containing the below fields, and signed using the verification method published as the authentication verification relationship in the requester's DID document. + +#### Header + +| Key | Description | +| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [`typ`](https://datatracker.ietf.org/doc/html/rfc7519#section-5.1) | JWT. Setting `typ` to `JWT` as recommended by the JWT spec provides a means to disambiguate among different types of signatures and tokens. | +| [`kid`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.4) | Fully qualified [VerificationMethod ID](https://www.w3.org/TR/did-core/#verification-methods). Used to locate the DID Document verification method utilized to verify the integrity of the JWT. | +| [`alg`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.1) | Cryptographic algorithm used to compute the JWT signature. | + + +#### Claims Set + +| Key | Description | +| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`aud`](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3) | The intended PFI's DID. Incorporating the `aud` claim limits the risk to just one PFI in case a request token is compromised, thereby reducing the surface area for misuse. | +| [`iss`](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1) | The requester's DID. Included to be informative, though it is technically duplicative as the `kid` also includes the requester's DID. | +| [`exp`](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4) | Expiration timestamp. Limits the amount of time a compromised request token can be used. | +| [`iat`](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6) | Indicates when the JWT was created. Included to be informative. | +| [`jti`](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7) | Used as a nonce to prevent replay attacks. The specification text should include more detail on how to prevent these attacks. | + +> [!NOTE] +> This bearer token is sufficient for authentication as it proves that the requester controls the private key associated to the DID that the requester is identifying as. + + +## Token Verification + +The receiver of the token must evaluate it to ensure its validity. A bearer token is valid if these conditions are met: + +- `exp` timestamp is not in the past +- `aud` is the DID of the receiving PFI's DID +- `kid` is resolved to be a valid DID whose DID Document verification method is used to verify the integrity of the JWT +- `jti` is not a nonce that was previously used by the same requester + +--- + # Offerings -The [`Offering`](../README.md#offering) resource is used to convey the currency pairs a PFI is _offering_. It includes information about payment methods and associated fees. +The [`Offering`](../protocol/README.md#offering) resource is used to convey the currency pairs a PFI is _offering_. It includes information about payment methods and associated fees. ## List Offerings @@ -169,11 +234,8 @@ Used to fetch offerings from a PFI ### Endpoint `GET /offerings` -### Authentication -No authentication required. - -### Authorization -No authorization required. Offerings are publicly accessible +### Protected +False ### Query Params | Param | Description | @@ -198,17 +260,18 @@ An exchange is a series of linked tbDEX messages between Alice and a PFI for a s ## Submit RFQ +### Description +Submits an RFQ (Request For Quote). Alice is asking the PFI to provide a Quote so she can evaluate it. + ### Endpoint `POST /exchanges/:exchange_id/rfq` -### Authentication -Refer to [Signature Verification Section]() of the tbDEX spec -### Authorization -No Authorization required to submit an RFQ +### Protected +False ### Request Body > [!IMPORTANT] -> See RFQ structure [here](../README.md#rfq-request-for-quote) +> See RFQ structure [here](../protocol/README.md#rfq-request-for-quote) ### Response | Status | Body | @@ -225,28 +288,43 @@ No Authorization required to submit an RFQ | 409 | RFQ already exists | | 409 | Exchange already exists | -## Get Quote +## Get Quote/Order/OrderStatus + +### Description +Retrieve the desired tbdex message type given an exchangeId. + ### Endpoint `GET /exchanges/:exchange_id/?messageType=quote` +### Protected +True + ### Response -| Status | Body | -| ------------------ | --------------------------------- | -| `200: OK` | `{ data: TbdexMessage[] }` | -| `400: Bad Request` | `{ errors: Error[] }` | +| Status | Body | +| ------------------ | --------------------------------------------------- | +| `200: OK` | `{ data: TbdexMessage[] }` | +| `400: Bad Request` | `{ errors: Error[] }` | ### Response Body > [!IMPORTANT] -> See Quote structure [here](../README.md#quote) +> See Quote structure [here](../protocol/README.md#quote) +> See Order structure [here](../protocol/README.md#order) +> See OrderStatus structure [here](../protocol/README.md#orderstatus) ## Submit Order +### Description +Submits the Order. Alice wants to accept the Quote and execute the transaction. + ### Endpoint `POST /exchanges/:exchange_id/order` +### Protected +False + ### Order Request Body > [!IMPORTANT] -> See Order structure [here](../README.md#order) +> See Order structure [here](../protocol/README.md#order) ### Response @@ -271,9 +349,12 @@ Closes the exchange. Indicates that Alice is no longer interested ### Endpoint `POST /exchanges/:exchange_id/close` +### Protected +False + ### Request Body > [!IMPORTANT] -> See Close structure [here](../README.md#close) +> See Close structure [here](../protocol/README.md#close) ### Response @@ -296,12 +377,11 @@ Closes the exchange. Indicates that Alice is no longer interested ### Description Retrieves the messages specified by ID and messageType -### Authentication -Uses DID authn via Bearer token in header. - ### Endpoint `GET /exchanges/:id` +### Protected +True ### Query Params | Param | Description | @@ -323,21 +403,21 @@ Uses DID authn via Bearer token in header. ## List Exchanges ### Description -Returns an array of tbdex message arrays - -### Authentication -Uses DID authn via Bearer token in header. +Returns an array of tbdex message arrays (a list of exchanges) ### Endpoint `GET /exchanges` +### Protected +True + ### Response -| Status | Body | -| ------------------ | -------------------------------------- | -| `200: OK. ` | `{ data: { TbdexMessage[][] } }` | -| `400: Bad Request` | `{ errors: Error[] }` | -| `404: Not Found` | N/A | -| `403: Forbidden` | N/A | +| Status | Body | +| ------------------ | -------------------------------- | +| `200: OK.` | `{ data: { TbdexMessage[][] } }` | +| `400: Bad Request` | `{ errors: Error[] }` | +| `404: Not Found` | N/A | +| `403: Forbidden` | N/A | ### Query Params @@ -345,7 +425,7 @@ Uses DID authn via Bearer token in header. | ----- | ------------------------ | | id | exchange id(s) to return | - +--- # References * JSON:API spec: https://jsonapi.org/format/ \ No newline at end of file