diff --git a/ecosystem/sep-0045.md b/ecosystem/sep-0045.md index a0c1b2825..514b210e6 100644 --- a/ecosystem/sep-0045.md +++ b/ecosystem/sep-0045.md @@ -2,7 +2,7 @@ ``` SEP: 0045 -Title: Stellar Web Authentication +Title: Stellar Web Authentication for Contracts Author: Status: Draft Created: 2024-10-08 @@ -13,82 +13,72 @@ Version: 0.1.0 ## Simple Summary This SEP defines the standard way for clients such as wallets or exchanges to create authenticated web sessions on -behalf of a user who holds a Stellar account. A wallet may want to authenticate with any web service which requires a -Stellar account ownership verification, for example, to upload KYC information to an anchor in an authenticated way as +behalf of a user who holds a Contract account. A wallet may want to authenticate with any web service which requires a +contract account ownership verification, for example, to upload KYC information to an anchor in an authenticated way as described in [SEP-12](sep-0012.md). -This SEP also supports authenticating users of shared, omnibus, or pooled Stellar accounts. Clients can use -[memos](#memos) or [muxed accounts](#muxed-accounts) to distinguish users or sub-accounts of shared accounts. +This SEP also supports authenticating users of shared contract accounts. Clients can use [memos](#memos) to distinguish +users or sub-accounts of shared accounts. + +This SEP is based on SEP-0010, but only supports contract accounts and aims to provide a JWT that is fully compatible +with existing protocols that use SEP-0010. ## Abstract -This protocol is a variation of mutual challenge-response, which uses Stellar transactions to encode challenges and -responses. +This protocol is a variation of mutual challenge-response, which uses Soroban authorization entries to encode challenges +and responses. It involves the following components: -- A **Home Domain**: a domain hosting a [SEP-1 stellar.toml](sep-0001.md) containing a `WEB_AUTH_ENDPOINT` (URL) and - `SIGNING_KEY` (`G...`). -- A **Server**: a server providing the `WEB_AUTH_ENDPOINT` that implements the GET and POST operations discussed in this - document. The server's domain may be the **Home Domain**, a sub-domain of the **Home Domain**, or a different domain. +- A **Home Domain**: a domain hosting a [SEP-1 stellar.toml](sep-0001.md) containing a `WEB_AUTH_ENDPOINT_SEP0045` (URL) + and `SIGNING_KEY` (`G...`). +- A **Server**: a server providing the `WEB_AUTH_ENDPOINT_SEP0045` that implements the GET and POST operations discussed + in this document. The server's domain may be the **Home Domain**, a sub-domain of the **Home Domain**, or a different + domain. - The `SIGNING_KEY` from the **Home Domain** is the **Server Account**. - A **Client Account**: the account being authenticated. - - A Stellar account (`G...`) or a muxed account (`M...`). - - If a Stellar account (`G...`), may be accompanied by a memo to scope the authentication to a user or sub-account of - the account. -- A **Client**: the software used by the holder of the **Client Account** being authenticated by the **Server**. -- A **Client Domain** (optional): a domain hosting a [SEP-1 stellar.toml](sep-0001.md) containing a `SIGNING_KEY` used - for [Verifying the Client Domain](#verifying-the-client-domain) - - The `SIGNING_KEY` from this domain is the **Client Domain Account** + - A Contract address (`C...`) that may be accompanied by a memo to scope the authentication to a user or sub-account + of the account. +- A **Web Auth Contract**: a contract that implements the `web_auth_verify` function. The contract must be deployed at + the `WEB_AUTH_CONTRACT_ID` address specified in the **Server**'s `stellar.toml`. The discovery flow is as follows: 1. The **Client** retrieves the `stellar.toml` from the **Home Domain** in accordance with [SEP-1 stellar.toml](sep-0001.md). -1. The **Client** looks up the `WEB_AUTH_ENDPOINT` and `SIGNING_KEY` (i.e. **Server Account**) from the `stellar.toml`. +1. The **Client** looks up the `WEB_AUTH_ENDPOINT_SEP0045`, `WEB_AUTH_CONTRACT_ID` and `SIGNING_KEY` (i.e. **Server + Account**) from the `stellar.toml`. The authentication flow is as follows: -1. The **Client** requests a unique [`challenge`](#challenge) transaction from the **Server**, which is represented as - specially formed Stellar transaction -1. If the request contains a `client_domain` parameter, the **Server** may fetch the **Client Domain Account** and - generate the challenge transaction with an additional Manage Data operation as described in the [Response](#response) - section. -1. The **Server** responds with the challenge transaction. -1. The **Client** verifies that the transaction has an invalid sequence number 0. This is extremely important to ensure - the transaction isn't malicious. -1. The **Client** verifies that the transaction is signed by the **Server Account** obtained through discovery flow. -1. The **Client** verifies that the transaction's first operation is a Manage Data operation that has its: - 1. Source account set to the **Client Account** - 1. Key set to ` auth` where the home domain is the **Home Domain**. - 1. Value set to a nonce value. -1. The **Client** verifies that if the transaction has a Manage Data operation with key `web_auth_domain` that it has: - 1. Source account set to the **Server Account**. - 1. Value set to the **Server**'s domain that the client requested the challenge from. -1. The **Client** verifies that if the transaction has other operations they are Manage Data operations and that their - source account is set to: - 1. The **Client Domain Account** if the Manage Data operation key is set to `client_domain` - 1. Otherwise, the **Server Account**. -1. If the client included a client domain in the request, and the transaction has a Manage Data operation with key - `client_domain`, the **Client** obtains a signature from the **Client Domain Account** and adds it to the challenge - transaction -1. The **Client** signs the transaction using the secret key(s) of the signer(s) for the **Client Account** -1. The **Client** submits the signed challenge back to the **Server** using [`token`](#token) endpoint -1. The **Server** checks that the **Client Account** exists -1. If the **Client Account** exists: -1. The **Server** gets the signers of the **Client Account** -1. The **Server** verifies that one or more signatures are from signers of the **Client Account**. -1. The **Server** verifies that there is only one additional signature from the **Server Account** -1. The **Server** verifies the weight provided by the signers of the **Client Account** meets the required threshold(s), - if any -1. If the **Client Account** does not exist (optional): -1. The **Server** verifies the signature count is two -1. The **Server** verifies that one signature is correct for the master key of the **Client Account** -1. The **Server** verified that the other signature is from the **Server Account** -1. If the transaction has a Manage Data operation with key `client_domain`, the **Server** verifies that the source - account of the operation signed the transaction and includes an additional `client_domain` claim in the JWT included - in the response -1. If the signatures check out, the **Server** responds with a [JWT](https://jwt.io) that represents the authenticated +1. The **Client** requests a unique [`challenge`](#challenge) from the **Server** which includes a list of Soroban + authorization entries and the server's signatures represented as XDR strings +1. The **Server** responds with the challenge +1. The **Client** verifies each authorization entry does not include additional sub-invocations +1. The **Client** verifies each authorization entry has a corresponding signature that is signed by the **Server + Account** obtained through the discovery flow. +1. The **Client** verifies each authorization entry's root invocation function that: + 1. Contract addresses matches the `WEB_AUTH_CONTRACT_ID` from the **Server**'s `stellar.toml` + 1. Function name is `web_auth_verify` +1. The **Client** verifies each authorization entry if the root invocation function's first argument matches the + **Client Account** from the request +1. The **Client** verifies each authorization entry that the root invocation function's third argument is the **Home + Domain** +1. The **Client** verifies each authorization entry that the root invocation function's fourth argument is the + **Server**'s domain +1. If the client included a client domain in the request, the **Client** verifies each authorization entry that the root + invocation function's sixth argument is the **Client Account** +1. The **Client** signs the first authorization entry using the secret key(s) of the signer(s) for the **Client + Account**. +1. The **Client** obtains a signature from the **Client Domain Account** for the second authorization entry if the + **Client** included a client domain in the request. +1. The **Client** submits the credentials along with the original challenge back to the **Server** using + [`token`](#token) endpoint +1. The **Server** verifies the integrity of the authorization entries and server signatures returned by the **Client** +1. The **Server** verifies the last argument, the nonce, is the same across all authorization entries and is unique +1. The **Server** constructs a transaction with a single Invoke Host Function operation using the credentials and the + challenge authorization entries returned by the client and simulates the transaction. +1. If the simulation succeeds, the **Server** responds with a [JWT](https://jwt.io) that represents the authenticated session The flow achieves several things: @@ -99,16 +89,15 @@ The flow achieves several things: - The **Server** can choose its own timeout for the authenticated session - The **Server** can choose required signing threshold(s) that must be met, if any - The **Server** can choose to include other application-specific claims -- The **Server** can choose to authenticate Stellar accounts that do not yet exist ## Authentication Endpoint The organization with a **Home Domain** indicates that it supports authentication via this protocol by specifying -`WEB_AUTH_ENDPOINT` in their [`stellar.toml`](sep-0001.md) file. This is how a wallet knows where to find the +`WEB_AUTH_ENDPOINT_SEP0045` in their [`stellar.toml`](sep-0001.md) file. This is how a wallet knows where to find the **Server**. A **Server** is required to implement the following behavior for the web authentication endpoint: -- [`GET `](#challenge): request a challenge (step 1) -- [`POST `](#token): exchange a signed challenge for session JWT (step 2) +- [`GET `](#challenge): request a challenge (step 1) +- [`POST `](#token): exchange a signed challenge for session JWT (step 2) ## Cross-Origin Headers @@ -125,105 +114,31 @@ implemented in all the endpoints that support Cross-Origin. ### Challenge -This endpoint must respond with a Stellar transaction signed by the **Server Account** that has an invalid sequence -number (0) and thus cannot be executed on the Stellar network. The **Client** can then sign the transaction using -standard Stellar libraries and submit it to [`token`](#token) endpoint to prove that it controls the **Client Account**. -This approach is compatible with hardware wallets such as Ledger. The **Client Application** must also verify the -server's signature to be sure the challenge is signed by the **Server Account**, that the home domain in the first -operation of the challenge is the **Home Domain**, and that the web auth domain in a subsequent operation is the -**Server** domain. +This endpoint must respond with authorization entries signed by the **Server Account**. The **Client** can then sign the +entries using standard Stellar libraries and submit it to [`token`](#token) endpoint to prove that it controls the +**Client Account**. This approach is compatible with hardware wallets such as Ledger. The **Client Application** must +also verify the server's signature to be sure the challenge is signed by the **Server Account**, that the home domain +argument in the function invocation is the **Home Domain**, and that the web auth domain argument in the function +invocation is the **Server** domain. #### Request ``` -GET +GET ``` -##### Request Header: - -Optionally, the server may forbid unauthorized calls to `GET `. This could be done to protect an -endpoint against bad actors or limit access only to allowed applications. - -In that case, the client must add an `Authorization` header, and the server should respond to all requests with missing -header with an error (see below). Server may choose to only accept calls from trusted applications, responding with an -error to all other requests (see below). - -Authorized `WEB_AUTH_ENDPOINT` accepts `Authorization Bearer: `, where token is [JSON Web Token](https://jwt.io/) -authorizing the request. - -Client must specify the following claims: - -- `iat` (the time at which the JWT was issued - [RFC7519, Section 4.1.6](https://tools.ietf.org/html/rfc7519#section-4.1.6)) — current timestamp (for example - `1530644093`) -- `exp` (the expiration time on or after which the JWT must not be accepted for processing, - [RFC7519, Section 4.1.4](https://tools.ietf.org/html/rfc7519#section-4.1.4)) -- `web_auth_endpoint` - should match the auth endpoint (``) -- All request's parameters (if specified in the request) - -Client must then correctly sign the payload with appropriate Stellar private key. To choose the private key client -application should follow this steps: - -- If `client_domain` is specified, the token must be signed with the **Client Domain Account** (i.e. [SEP-1] defined - `SIGNING_KEY`). (See [Verifying the Client Domain](#verifying-the-client-domain) section for more information) -- Otherwise, the token must be signed with a private key of the **Client Account** passed in the `account` field. - -Token then should be signed with the selected private key, using **ed25519** algorithm. An appropriate header must be -included - -```json -{ - "alg": "EdDSA" -} -``` - -Example of the valid signed token in base64 format: - -``` -eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3MTE2NDg0ODYsImV4cCI6MTcxMTY0OTM4NiwiYWNjb3VudCI6IkdDNlVDWFZUQU1ORzVKTE9NWkJTQ05ZWFZTTk5GSEwyM1NKUFlPT0ZKRTJBVllERFMyRkZUNDVDIiwiY2xpZW50X2RvbWFpbiI6ImV4YW1wbGUtd2FsbGV0LnN0ZWxsYXIub3JnIiwid2ViX2F1dGhfZW5kcG9pbnQiOiJodHRwczovL2V4YW1wbGUuY29tL3NlcDEwL2F1dGgifQ.UQt8FpUK-BlnFw35o8Ke4GDOoCrMe9ztEx4_TGQ06XhMgUbn_b7EMPMVLWJ8RRNgSk2dNhyGUgIbhKzKtWtBBw -``` - -This token was issued for the request URL -`https://example.com/sep10/auth?account=GC6UCXVTAMNG5JLOMZBSCNYXVSNNFHL23SJPYOOFJE2AVYDDS2FFT45C&client_domain=example-wallet.stellar.org` -and signed with the `GCADAIACE6CRWGOB3HJRXIOHMQEUKPNLAUIYFIE26F3OK72RSHGAMARK` key, belonging to a hypothetical domain -`example-wallet.stellar.org` - -Another example of the valid signed token: - -``` -eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3MTE2NDg0MjIsImV4cCI6MTcxMTY0OTMyMiwiYWNjb3VudCI6IkdDWFhINkFZSlVWVERHSUhUNDJPWk5NRjNMSENWNERPS0NYNkhIREtXRUNVWllYRFpTV1pONkhTIiwibWVtbyI6IjEyMzQ1NjciLCJ3ZWJfYXV0aF9lbmRwb2ludCI6Imh0dHBzOi8vZXhhbXBsZS5jb20vc2VwMTAvYXV0aCJ9.23TJFUWtadeNWW0N1mjk4gWUZJRTOnrxfs3gahNRuhrKHRbHonrksri6lzJdIvO71a_Ad851necSO6TTXB_IBw -``` - -The token was issued for the request URL -`https://example.com/sep10/auth?account=GCXXH6AYJUVTDGIHT42OZNMF3LHCV4DOKCX6HHDKWECUZYXDZSWZN6HS&memo=1234567`. This -time the token is signed with a **Client Account** () private key - -##### Verifying request header - -When server receives a request with an `Authorization` header, server should validate the token before processing with -issuing a challenge transaction. -First, server must use the same steps of choosing the public key (see above), and verify **ed25519** signature of the -token. Next, server must verify JWT claims: - -1. `web_auth_endpoint` must match `` -2. Query parameters from claims must match request's parameters. - -If `client_domain` is provided in the request, but the Server doesn't support `client_domain` verification, Server -should respond with an error. - ##### Request Parameters: -| Name | Type | Description | -| --------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `account` | `G...` string | The **Client Account**, which can be a stellar account (`G...`) or muxed account (`M...`) that the **Client** wishes to authenticate with the **Server**. | -| `memo` | string | (optional) The memo to attach to the challenge transaction. Only permitted if a Stellar account (`G...`) is used. The memo must be of type `id`. Other memo types are not supported. See the [Memo](#memos) section for details. | -| `home_domain` | string | (optional) a **Home Domain**. Servers that generate tokens for multiple **Home Domain**s can use this parameter to identify which home domain the **Client** hopes to authenticate with. If not provided by the **Client**, the **Server** should assume a default for backwards compatibility with older **Clients**. | -| `client_domain` | string | (optional) a **Client Domain**. Supplied by **Clients** that intend to verify their domain in addition to the **Client Account**. See [Verifying the Client Domain](#verifying-the-client-domain). **Servers** should ignore this parameter if the **Server** does not support **Client Domain** verification, or the **Server** does not support verification for the specific **Client Domain** included in the request. | +| Name | Type | Description | +| ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `address` | `C...` string | The **Client Account**, which is a Contract address that the **Client** wishes to authenticate with the **Server**. | +| `memo` | string | (optional) The memo to attach to the challenge transaction. The memo must be of type `id`. Other memo types are not supported. See the [Memo](#memos) section for details. | +| `home_domain` | string | A **Home Domain**. Servers that generate tokens for multiple **Home Domain**s can use this parameter to identify which home domain the **Client** hopes to authenticate with. | Example: ``` -GET https://auth.example.com/?account=GCIBUCGPOHWMMMFPFTDWBSVHQRT4DIBJ7AD6BZJYDITBK2LCVBYW7HUQ +GET https://auth.example.com/?address=CCIBUCGPOHWMMMFPFTDWBSVHQRT4DIBJ7AD6BZJYDITBK2LCVBYW7HUQ ``` #### Response @@ -232,43 +147,43 @@ GET https://auth.example.com/?account=GCIBUCGPOHWMMMFPFTDWBSVHQRT4DIBJ7AD6BZJYDI On success the endpoint must return `200 OK` HTTP status code and a JSON object with these fields: -- `transaction`: an XDR-encoded Stellar transaction with the following: - - source account set to the **Server Account** - - invalid sequence number (set to 0) so the transaction cannot be run on the Stellar network - - time bounds: `{min: now(), max: now() + 900 }` (we recommend expiration of 15 minutes to give the **Client** time to - sign transaction) - - memo: the `memo` value passed by the **Client** if specified, omitted otherwise - - operations: - - `manage_data(source: client account, key: ' auth', value: random_nonce())` - - The source account is the **Client Account** - - The value of key is the **Home Domain**, followed by `auth`. It can be at most 64 characters. - - The value must be 64 bytes long. It contains a 48 byte cryptographic-quality random string encoded using base64 - (for a total of 64 bytes after encoding). - - subsequent operations, order unimportant: - - `manage_data(source: server account, key: 'web_auth_domain', value: web_auth_domain)` - - The source account is the **Server Account** - - The value is the **Server**'s domain. It can be at most 64 characters. - - (optional) `manage_data(source: client domain account, key: 'client_domain', value: client_domain)` - - The source account is the **Client Domain Account** - - Add this operation if the server supports [Verifying the Client Domain](#verifying-the-client-domain) and the - client provided a `client_domain` parameter in the request. - - zero or more `manage_data(source: server account, ...)` reserved for future use - - signature by the **Server Account** +- `authorization_entries`: a list of XDR-encoded `SorobanAuthorizationEntry`. The first entry is must be signed by the + **Client Account**. There is an optional second entry that must be signed by the **Client Domain Account** if the + **Client** included a client domain in the request. Each entry's `root_invocation` function is a `contract_fn` with + the following with no additional sub invocations: + - `contract_address` is the `WEB_AUTH_CONTRACT_ID` from the **Server**'s `stellar.toml` + - `function_name` is `web_auth_verify` + - `args`: + - `account` matches the **Client Account** from the request + - `memo` matches the `memo` from the request + - `home_domain` matches the `home_domain` from the request + - `web_auth_domain` matches the **Server**'s domain + - `client_domain` matches the **Client**'s domain + - `client_domain_address` is the **Client Domain**'s address +- `server_signatures`: a list of base64 hex encoded signatures of the `SorobanAuthorizationEntry` hashes by the **Server + Account**. The signature at index `i` corresponds to the `SorobanAuthorizationEntry` at index `i`. - `network_passphrase`: (optional but recommended) Stellar network passphrase used by the **Server**. This allows a **Client** to verify that it's using the correct passphrase when signing and is useful for identifying when a **Client** or **Server** have been configured incorrectly. Example: + + ```json { - "transaction": "AAAAAgAAAADIiRu2BrqqeOcP28PWCkD4D5Rjjsqh71HwvqFX+F4VXAAAAGQAAAAAAAAAAAAAAAEAAAAAXzrUcQAAAABfOtf1AAAAAAAAAAEAAAABAAAAAEEB8rhqNa70RYjaNnF1ARE2CbL50iR9HPXST/fImJN1AAAACgAAADB0aGlzaXNhdGVzdC5zYW5kYm94LmFuY2hvci5hbmNob3Jkb21haW4uY29tIGF1dGgAAAABAAAAQGdGOFlIQm1zaGpEWEY0L0VJUFZucGVlRkxVTDY2V0tKMVBPYXZuUVVBNjBoL09XaC91M2Vvdk54WFJtSTAvQ2UAAAAAAAAAAfheFVwAAABAheKE1HjGnUCNwPbX8mz7CqotShKbA+xM2Hbjl6X0TBpEprVOUVjA6lqMJ1j62vrxn1mF3eJzsLa9s9hRofG3Ag==", + "authorization_entries": [ + "AAAAAQAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0ukNc84Ms0ZvgAAAAAAAAAAEAAAAAAAAAAeA7wfSg10yaQYZDRmQeyqsepsS/Mb0rbMQxRgDoSVdWAAAAD3dlYl9hdXRoX3ZlcmlmeQAAAAAGAAAADgAAADhDRFlPUUpMS1pXSFoyQ1ZONDNFVkVRTkRMRU41NDRJR0NPNUE1MlVHNFlTNktETjVRUTJMVVdLWQAAAA4AAAADMTIzAAAAAA4AAAAcaHR0cDovL2xvY2FsaG9zdDo4MDgwL2MvYXV0aAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAA4AAAALZXhhbXBsZS5jb20AAAAAAQAAAAA=" + ], + "server_signatures": [ + "848FF1A729728A5AFF509CEE54AB4BAB1403634B74124A735E8D5F0A67AE45C294A846D24CDA358F139064A3781BFEAA717C98D832BFF2830A0D3ED2C4444C0C" + ], "network_passphrase": "Public Global Stellar Network ; September 2015" } ``` You can examine the example challenge transaction in the -[XDR Viewer](https://laboratory.stellar.org/#xdr-viewer?input=AAAAAgAAAADIiRu2BrqqeOcP28PWCkD4D5Rjjsqh71HwvqFX%2BF4VXAAAAGQAAAAAAAAAAAAAAAEAAAAAXzrUcQAAAABfOtf1AAAAAAAAAAEAAAABAAAAAEEB8rhqNa70RYjaNnF1ARE2CbL50iR9HPXST%2FfImJN1AAAACgAAADB0aGlzaXNhdGVzdC5zYW5kYm94LmFuY2hvci5hbmNob3Jkb21haW4uY29tIGF1dGgAAAABAAAAQGdGOFlIQm1zaGpEWEY0L0VJUFZucGVlRkxVTDY2V0tKMVBPYXZuUVVBNjBoL09XaC91M2Vvdk54WFJtSTAvQ2UAAAAAAAAAAfheFVwAAABAheKE1HjGnUCNwPbX8mz7CqotShKbA%2BxM2Hbjl6X0TBpEprVOUVjA6lqMJ1j62vrxn1mF3eJzsLa9s9hRofG3Ag%3D%3D&type=TransactionEnvelope) +[XDR Viewer](https://lab.stellar.org/xdr/view?$=network$id=testnet&label=Testnet&horizonUrl=https:////horizon-testnet.stellar.org&rpcUrl=https:////soroban-testnet.stellar.org&passphrase=Test%20SDF%20Network%20/;%20September%202015;&xdr$blob=AAAAAQAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0ukNc84Ms0ZvgAAAAAAAAAAEAAAAAAAAAAeA7wfSg10yaQYZDRmQeyqsepsS//Mb0rbMQxRgDoSVdWAAAAD3dlYl9hdXRoX3ZlcmlmeQAAAAAGAAAADgAAADhDRFlPUUpMS1pXSFoyQ1ZONDNFVkVRTkRMRU41NDRJR0NPNUE1MlVHNFlTNktETjVRUTJMVVdLWQAAAA4AAAADMTIzAAAAAA4AAAAcaHR0cDovL2xvY2FsaG9zdDo4MDgwL2MvYXV0aAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAA4AAAALZXhhbXBsZS5jb20AAAAAAQAAAAA=&type=SorobanAuthorizationEntry;;) ##### Error @@ -280,69 +195,30 @@ Every other HTTP status code will be considered an error. For example: } ``` -When authorization header is required by the server, but is not provided in the request, the anchor should return status -code 401 (Unauthorized) and return comprehensive error message in the response body. For example: - -```json -{ - "error": "Missing authorization header" -} -``` - -When authorization header is invalid, status code 400 (Bad Request) should be returned, explaining why header is -invalid. - -```json -{ - "error": "Provided authorization JWT has been expired" -} -``` - -Finally, when authorization header is valid, but the server does not allow the application to use the endpoint, status -code 403 (Forbidden) should be returned, - -```json -{ - "error": "Application example-wallet.stellar.org is not allowed to authenticate." -} -``` - ### Token This endpoint accepts a signed challenge transaction, validates it and responds with a session [JSON Web Token](https://jwt.io/) authenticating the account. -The **Client** submits a challenge transaction (that was previously returned by the [`challenge`](#challenge) endpoint) -as a HTTP POST request to `WEB_AUTH_ENDPOINT` using one of the following formats (both should be equally supported by -the server): +The **Client** submits a challenge (that was previously returned by the [`challenge`](#challenge) endpoint) as a HTTP +POST request to `WEB_AUTH_ENDPOINT_SEP0045` using one of the following formats (both should be equally supported by the +server): -- Content-Type: `application/x-www-form-urlencoded`, body: `transaction=`) -- Content-Type: `application/json`, body: `{"transaction": ""}` +- Content-Type: `application/x-www-form-urlencoded`, body: + `authorization_entries=,authorization_entries=,server_signatures=,server_signatures=,credentials=,credentials=`) +- Content-Type: `application/json`, body: + `{"authorization_entries": ["", ""], "server_signatures": ["", ""], "credentials": ["", ""]}` To validate the challenge transaction the following steps are performed by the **Server**. If any of the listed steps fail, then the authentication request must be rejected — that is, treated by the **Server** as an invalid input. -- decode the received input as a base64-urlencoded XDR representation of Stellar transaction envelope; -- verify that transaction source account is equal to the **Server Account** -- verify that transaction has time bounds set, and that current time is between the minimum and maximum bounds; -- verify that transaction contains at least one operation; -- verify that transaction's first operation: - - is a Manage Data operation - - has a non-null source account -- verify that transaction envelope has a correct signature by the **Server Account** -- if the first operation's source account exists: - - verify that the remaining signature count is one or more; - - verify that remaining signatures on the transaction are signers of the **Client Account** - - verify that remaining signatures are correct; - - verify that remaining signatures provide weight that meets the required threshold(s), if any; -- if the first operation's source account does not exist: - - verify that remaining signature count is one; - - verify that remaining signature is correct for the master key of the **Client Account** -- if the transaction contains a Manage Data operation with the key `client_domain`: - - verify that the transaction was signed by the source account of the Manage Data operation -- verify that transaction containing additional Manage Data operations have their source account set to the **Server - Account**; -- verify that transaction sequenceNumber is equal to zero; +1. For each entry `i` in `authorization_entries`: + 1. decode the received input as a base64-urlencoded XDR representation of a `SorobanAuthorizationEntry`; + 1. verify that the `i`th signature corresponds to the `i`th authorization entry and was signed by the **Server + Account**; +1. Construct a transaction with a single Invoke Host Function operation using the credentials and the challenge + authorization entries returned by the client; +1. Simulate the transaction and verify that it succeeds. The verification process confirms that the **Client** controls the **Client Account**. Depending on your application this may mean complete signing authority, some threshold of control, or being a signer of the account. See @@ -354,18 +230,14 @@ Upon successful verification, **Server** responds with a session JWT, containing — a [Uniform Resource Identifier (URI)] for the issuer (`https://example.com` or `https://example.com/G...`) - `sub` (the principal that is the subject of the JWT, [RFC7519, Section 4.1.2](https://tools.ietf.org/html/rfc7519#section-4.1.2)) — there are several possible formats: - - If the **Client Account** is a muxed account (`M...`), the `sub` value should be the muxed account (`M...`). - - If the **Client Account** is a stellar account (`G...`): - - And, a memo was attached to the challenge transaction, the `sub` should be the stellar account appended with the - memo, separated by a colon (`G...:17509749319012223907`). - - Otherwise, the `sub` value should be Stellar account (`G...`). + - If a memo was attached to the challenge transaction, the `sub` should be the stellar account appended with the memo, + separated by a colon (`C...:17509749319012223907`). + - Otherwise, the `sub` value should be Stellar account (`C...`). - `iat` (the time at which the JWT was issued [RFC7519, Section 4.1.6](https://tools.ietf.org/html/rfc7519#section-4.1.6)) — current timestamp (`1530644093`) - `exp` (the expiration time on or after which the JWT must not be accepted for processing, [RFC7519, Section 4.1.4](https://tools.ietf.org/html/rfc7519#section-4.1.4)) — a server can pick its own expiration period for the token (`1530730493`) -- `client_domain` - (optional) a nonstandard JWT claim containing the client home domain, included if the challenge - transaction contained a `client_domain` (see [Verifying the Client Domain](#verifying-the-client-domain)) The JWT may contain other claims specific to your application, see [RFC7519]. @@ -382,9 +254,11 @@ POST Request Parameters: -| Name | Type | Description | -| ------------- | ------ | --------------------------------------------------- | -| `transaction` | string | the base64 encoded signed challenge transaction XDR | +| Name | Type | Description | +| ----------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `authorization_entries` | string | a list of base64 encoded authorization entry XDRs returned in the challenge response | +| `server_signatures` | string | a list of base64 hex encoded signatures of the `authorization_entry` hashes returned in the challenge response. The signature at index `i` corresponds to the `authorization_entries` at index `i` | +| `credentials` | list | a list of `SorobanCredentials` containing the signatures for each `authorization_entry`. The credentials at index `i` corresponds to the `authorization_entries` at index `i` | Example: @@ -392,11 +266,11 @@ Example: POST https://auth.example.com/ Content-Type: application/json -{"transaction": "AAAAAgAAAADIiRu2BrqqeOcP28PWCkD4D5Rjjsqh71HwvqFX+F4VXAAAAGQAAAAAAAAAAAAAAAEAAAAAXzrUcQAAAABfOtf1AAAAAAAAAAEAAAABAAAAAEEB8rhqNa70RYjaNnF1ARE2CbL50iR9HPXST/fImJN1AAAACgAAADB0aGlzaXNhdGVzdC5zYW5kYm94LmFuY2hvci5hbmNob3Jkb21haW4uY29tIGF1dGgAAAABAAAAQGdGOFlIQm1zaGpEWEY0L0VJUFZucGVlRkxVTDY2V0tKMVBPYXZuUVVBNjBoL09XaC91M2Vvdk54WFJtSTAvQ2UAAAAAAAAAAvheFVwAAABAheKE1HjGnUCNwPbX8mz7CqotShKbA+xM2Hbjl6X0TBpEprVOUVjA6lqMJ1j62vrxn1mF3eJzsLa9s9hRofG3AsiYk3UAAABArIrkvqmA0V9lIZcVyCUdja6CiwkPwsV8BfI4CZOyR1Oq7ysvNJWwY0G42dpxN9OP1qz4dum8apG2hqvxVWjkDQ=="} +{"authorization_entries": ["AAAAAQAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0um6s4olInY8EAAAAAAAAAAEAAAAAAAAAAeA7wfSg10yaQYZDRmQeyqsepsS/Mb0rbMQxRgDoSVdWAAAAD3dlYl9hdXRoX3ZlcmlmeQAAAAAGAAAADgAAADhDRFlPUUpMS1pXSFoyQ1ZONDNFVkVRTkRMRU41NDRJR0NPNUE1MlVHNFlTNktETjVRUTJMVVdLWQAAAA4AAAADMTIzAAAAAA4AAAAcaHR0cDovL2xvY2FsaG9zdDo4MDgwL2MvYXV0aAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAAEAAAABAAAAAA=="],"server_signatures": ["0B6E3C5A1987A2459970A3F16551A34EB6756CEB97C2F7A8B8923BBB37671318003EA78A47C4C2484FC21303759A4560CF8815ED1B252DDBAE6772DEFD606F06"],"credentials":["AAAAAQAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0um6s4olInY8EAAWMHgAAABAAAAABAAAAAQAAABEAAAABAAAAAgAAAA8AAAAKcHVibGljX2tleQAAAAAADQAAACCLcZbWB5Tc+LwIlJMazXz6KECPC89cSo589hUfJAOrhwAAAA8AAAAJc2lnbmF0dXJlAAAAAAAADQAAAEC4vXPFDsGlKMFLfEvagmDYZ8x+A0VhH0RlFPgdri/PJHjjsC6bO2nusUPNCxcjt2mX4yxFbZs48d0dSAuXijEN"]} ``` -You can examine the example signed challenge transaction in the -[XDR Viewer](https://laboratory.stellar.org/#xdr-viewer?input=AAAAAgAAAADIiRu2BrqqeOcP28PWCkD4D5Rjjsqh71HwvqFX%2BF4VXAAAAGQAAAAAAAAAAAAAAAEAAAAAXzrUcQAAAABfOtf1AAAAAAAAAAEAAAABAAAAAEEB8rhqNa70RYjaNnF1ARE2CbL50iR9HPXST%2FfImJN1AAAACgAAADB0aGlzaXNhdGVzdC5zYW5kYm94LmFuY2hvci5hbmNob3Jkb21haW4uY29tIGF1dGgAAAABAAAAQGdGOFlIQm1zaGpEWEY0L0VJUFZucGVlRkxVTDY2V0tKMVBPYXZuUVVBNjBoL09XaC91M2Vvdk54WFJtSTAvQ2UAAAAAAAAAAvheFVwAAABAheKE1HjGnUCNwPbX8mz7CqotShKbA%2BxM2Hbjl6X0TBpEprVOUVjA6lqMJ1j62vrxn1mF3eJzsLa9s9hRofG3AsiYk3UAAABArIrkvqmA0V9lIZcVyCUdja6CiwkPwsV8BfI4CZOyR1Oq7ysvNJWwY0G42dpxN9OP1qz4dum8apG2hqvxVWjkDQ%3D%3D&type=TransactionEnvelope) +You can examine the example credentials in the +[XDR Viewer](https://lab.stellar.org/xdr/view?$=network$id=testnet&label=Testnet&horizonUrl=https:////horizon-testnet.stellar.org&rpcUrl=https:////soroban-testnet.stellar.org&passphrase=Test%20SDF%20Network%20/;%20September%202015;&xdr$blob=AAAAAQAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0um6s4olInY8EAAWMHgAAABAAAAABAAAAAQAAABEAAAABAAAAAgAAAA8AAAAKcHVibGljX2tleQAAAAAADQAAACCLcZbWB5Tc+LwIlJMazXz6KECPC89cSo589hUfJAOrhwAAAA8AAAAJc2lnbmF0dXJlAAAAAAAADQAAAEC4vXPFDsGlKMFLfEvagmDYZ8x+A0VhH0RlFPgdri//PJHjjsC6bO2nusUPNCxcjt2mX4yxFbZs48d0dSAuXijEN&type=SorobanCredentials;;) #### Response @@ -409,6 +283,8 @@ status code and a JSON object with the following fields: Example: + + ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJHQTZVSVhYUEVXWUZJTE5VSVdBQzM3WTRRUEVaTVFWREpIREtWV0ZaSjJLQ1dVQklVNUlYWk5EQSIsImp0aSI6IjE0NGQzNjdiY2IwZTcyY2FiZmRiZGU2MGVhZTBhZDczM2NjNjVkMmE2NTg3MDgzZGFiM2Q2MTZmODg1MTkwMjQiLCJpc3MiOiJodHRwczovL2ZsYXBweS1iaXJkLWRhcHAuZmlyZWJhc2VhcHAuY29tLyIsImlhdCI6MTUzNDI1Nzk5NCwiZXhwIjoxNTM0MzQ0Mzk0fQ.8nbB83Z6vGBgC1X9r3N6oQCFTBzDiITAfCJasRft0z0" @@ -426,59 +302,46 @@ Every other HTTP status code will be considered an error. For example: } ``` -## Verification - -The verification process confirms that a **Client** controls the **Client Account**. Depending on your application this -may mean complete signing authority, some threshold of control, or being a signer of the account. - -An account's master key may not meet any threshold of control or could have had its weight reduced to zero. Most -applications should not assume possession of the master key is possession of an account. - -An account's signers may include third-party services providing services to the account holder of the **Client -Account**. Authenticating accounts with less than any threshold may allow a third-party to authenticate. - -An account's signers may include the **Server Account** if the server is a signer for the account. When determining the -weight of the remaining sigantures the signature from the **Server Account** should be explicitly excluded. A **Server** -should not assist in authentication. - -The **Server** should only issue a JWT if the appropriate thresholds are met, but if a **Server** is supporting a -variety of applications it may choose to use additional application specific claims to capture the threshold of control -the **Client** has proven. - -### Verifying Authority to Move Funds - -A **Server** that needs to verify that the **Client** has authority aligned with the capability to move money out of an -**Client Account** can verify that the medium threshold is met. It should do this by checking that the sum of the -weights of the challenge transaction signers is equal or greater to the medium threshold. - -#### Example - -An anchor implementing [SEP-24] will let an authenticated **Client** define the destination of withdrawn funds. This -level of control is similar to the capability to choose the destination of payments on the network which require a -medium threshold. - -[SEP-24]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md - -### Verifying Complete Authority - -A **Server** that needs to verify the **Client** has complete authority of an **Client Account** should verify that the -weight of the client signatures meet the high threshold. It should do this by checking that the sum of the weights is -equal or greater to the high threshold. - -### Verifying Being a Signer - -A **Server** may choose to issue JWTs for less than all thresholds and based on any other application specific logic. -It's important to keep in mind that a Stellar account may have third-parties who are signers. Authenticating accounts -with less than any threshold may allow a third-party to authenticate. - -### Verifying Accounts that Do Not Exist +## Web Auth Contract + +The **Server** must deploy a contract at the `WEB_AUTH_CONTRACT_ID` address specified in the **Server**'s +`stellar.toml`. This allows the **Server** to customize the authentication logic such as client domain verification and +nonce verification. + +The contract must implement the `web_auth_verify` function with the following signature and call the `require_auth` +function on the `address`. Optionally, the contract can require a signature from the `client_domain_address` if the +**Server** supports client domain verification. The contract should ignore all other arguments. + +```rust +#[contract] +pub struct WebAuthContract; + +#[contractimpl] +impl WebAuthContract { + pub fn web_auth_verify( + _env: Env, + address: Address, + _memo: Option, // IGNORED + _home_domain: Option, // IGNORED + _web_auth_domain: Option, // IGNORED + _client_domain: Option, // IGNORED + client_domain_address: Option
, + _nonce: Option, // IGNORED, used by the Server to ensure challenge is unique + ) { + address.require_auth(); + // Optional: require a signature from the client domain address + if let Some(client_domain_address) = client_domain_address { + client_domain_address.require_auth(); + } + } +} +``` -A **Server** that needs to support validating accounts that do not exist can require a signature of the master key of -the account address for accounts that do not exist. +## Verification ### Verifying the Client Domain -A web service requiring SEP-10 authentication may want to attribute each HTTP request made to it to a specific +A web service requiring SEP-45 authentication may want to attribute each HTTP request made to it to a specific **Client** software. For example, a web service may want to offer reduced fees for the users of a specific **Client**. In order to use this optional feature, the organization that provides the **Client** must host a @@ -489,7 +352,7 @@ Domain**. This setup allows the **Server** to verify that the challenge returned by the **Client** is also signed with the **Client Domain Account**, proving that the **Client** is associated with the **Client Domain**. Web services requiring -SEP-10 authentication can now attribute requests made with the resulting JWT to the **Client Domain** that signed the +SEP-45 authentication can now attribute requests made with the resulting JWT to the **Client Domain** that signed the challenge. **Servers** may chose which **Client Domains** to verify. If the **Client** requests verification of its domain but the @@ -505,18 +368,11 @@ other. Two users who are authenticated with the same Stellar account but differe separation as two users who are authenticated with different Stellar accounts. Typically the sessions are unique users who share a single Stellar account, sometimes called an omnibus or pooled account. -The `memo` parameter supported in `GET ` API calls is used to communicate to the **Server** that the -client intends to authenticate a Stellar account with an additional claim that the account is shared and that the user -should be identified using the `account` and `memo` parameters, instead of only using `account`. The value of the `memo` -passed will ultimately be added to the decoded JWT's `sub` field, separated from the account address by a colon (`:`). - -### Muxed Accounts - -Conceptually, a Stellar account and memo (of type `id`) is equivalent to a muxed account (`M...`) defined in the -protocol by [CAP-0027](../core/cap-0027.md) and standardized for use in the ecosystem in [SEP-0023](sep-0023.md). If a -SEP-10 implementation supports the use of memos to identify users of shared accounts, it is highly recommended to also -support the muxed account address format. Muxed accounts will become the primary method for identifying a user of a -shared, omnibus, or pooled account. +The `memo` parameter supported in `GET ` API calls is used to communicate to the **Server** +that the client intends to authenticate a Stellar account with an additional claim that the account is shared and that +the user should be identified using the `address` and `memo` parameters, instead of only using `address`. The value of +the `memo` passed will ultimately be added to the decoded JWT's `sub` field, separated from the account address by a +colon (`:`). ## JWT Expiration @@ -529,14 +385,14 @@ could have a poor user experience. ## A convention for signatures -Signatures in Stellar involve both the secret key of the signer and the passphrase of the network. SEP-10 clients and +Signatures in Stellar involve both the secret key of the signer and the passphrase of the network. SEP-45 clients and servers must use the following convention when deciding what network passphrase to use for signing and verifying -signatures in SEP-10: +signatures in SEP-45: - If the server is for testing purposes or interacts with the Stellar testnet, use the Stellar testnet passphrase. - Otherwise, use the Stellar pubnet passphrase. -This convention ensures that SEP-10 clients and servers can use the same passphrase as they're using for interacting +This convention ensures that SEP-45 clients and servers can use the same passphrase as they're using for interacting with the Stellar network. The client can examine the `network_passphrase` (if defined) that the server includes in its response from the challenge @@ -550,6 +406,10 @@ of best current practices when using JWTs: [IETF JWT BCP]. [IETF JWT BCP]: https://tools.ietf.org/wg/oauth/draft-ietf-oauth-jwt-bcp/ [SEP-1]: sep-0001.md +## Implementations + +- None + ## Changelog - `0.1.0`: Initial draft