From f36707cf1277d59c0dcab8b215806f5d9d478779 Mon Sep 17 00:00:00 2001 From: philipliu Date: Wed, 9 Oct 2024 15:58:54 -0400 Subject: [PATCH] Initial draft --- ecosystem/sep-0045.md | 496 ++++++++++++++++-------------------------- 1 file changed, 192 insertions(+), 304 deletions(-) diff --git a/ecosystem/sep-0045.md b/ecosystem/sep-0045.md index a0c1b2825..e5f2d6b50 100644 --- a/ecosystem/sep-0045.md +++ b/ecosystem/sep-0045.md @@ -2,93 +2,73 @@ ``` SEP: 0045 -Title: Stellar Web Authentication -Author: +Title: Stellar Web Authentication for Contract Accounts +Author: Philip Liu <@philipliu>, Marcelo Salloum <@marcelosalloum>, Leigh McCulloch <@leighmcculloch> +Track: Standard Status: Draft Created: 2024-10-08 Updated: 2024-10-08 Version: 0.1.0 +Discussion: TBA ``` ## 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-10](sep-0010.md), but only supports contract accounts and aims to provide a JWT that is fully +compatible with existing protocols that use SEP-10. ## 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 as described in this document. 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 that each authorization entry is signed by the **Server Account**. +1. The **Client** verifies that each authorization entry does not contain any sub-invocations. +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: @@ -97,18 +77,16 @@ The flow achieves several things: - The **Client** can verify that the **Server** holds the secret key to the **Server Account** - The **Server** can verify that the **Client** holds the secret key(s) to signer(s) of the **Client Account** - 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 +103,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 -``` - -##### 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" -} +GET ``` -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,28 +136,21 @@ 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. @@ -262,13 +159,18 @@ Example: ```json { - "transaction": "AAAAAgAAAADIiRu2BrqqeOcP28PWCkD4D5Rjjsqh71HwvqFX+F4VXAAAAGQAAAAAAAAAAAAAAAEAAAAAXzrUcQAAAABfOtf1AAAAAAAAAAEAAAABAAAAAEEB8rhqNa70RYjaNnF1ARE2CbL50iR9HPXST/fImJN1AAAACgAAADB0aGlzaXNhdGVzdC5zYW5kYm94LmFuY2hvci5hbmNob3Jkb21haW4uY29tIGF1dGgAAAABAAAAQGdGOFlIQm1zaGpEWEY0L0VJUFZucGVlRkxVTDY2V0tKMVBPYXZuUVVBNjBoL09XaC91M2Vvdk54WFJtSTAvQ2UAAAAAAAAAAfheFVwAAABAheKE1HjGnUCNwPbX8mz7CqotShKbA+xM2Hbjl6X0TBpEprVOUVjA6lqMJ1j62vrxn1mF3eJzsLa9s9hRofG3Ag==", - "network_passphrase": "Public Global Stellar Network ; September 2015" + "authorization_entries": [ + "AAAAAQAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0uiUmvl6zrExtAAAAAAAAAAEAAAAAAAAAAahbWC88MmIccL/85Jd6Myxjq5sHF+2dnq1+2H8osKvEAAAAD3dlYl9hdXRoX3ZlcmlmeQAAAAAHAAAAEgAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0ugAAAA4AAAADMTIzAAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAAEAAAABAAAADgAAAAozMDc4NjE1NjE1AAAAAAAA" + ], + "server_signatures": [ + "da46598d636f67fd59ad7dd319807434cd1b44e9c2177fb3ff179ed3702302cfccea30e1c234b6196a330dc05153e23dc51fbe44591d735a85dc4143009e4301" + ], + "network_passphrase": "Test SDF 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 +182,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,11 +217,9 @@ 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, @@ -382,21 +243,33 @@ 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: -``` +```json POST https://auth.example.com/ Content-Type: application/json -{"transaction": "AAAAAgAAAADIiRu2BrqqeOcP28PWCkD4D5Rjjsqh71HwvqFX+F4VXAAAAGQAAAAAAAAAAAAAAAEAAAAAXzrUcQAAAABfOtf1AAAAAAAAAAEAAAABAAAAAEEB8rhqNa70RYjaNnF1ARE2CbL50iR9HPXST/fImJN1AAAACgAAADB0aGlzaXNhdGVzdC5zYW5kYm94LmFuY2hvci5hbmNob3Jkb21haW4uY29tIGF1dGgAAAABAAAAQGdGOFlIQm1zaGpEWEY0L0VJUFZucGVlRkxVTDY2V0tKMVBPYXZuUVVBNjBoL09XaC91M2Vvdk54WFJtSTAvQ2UAAAAAAAAAAvheFVwAAABAheKE1HjGnUCNwPbX8mz7CqotShKbA+xM2Hbjl6X0TBpEprVOUVjA6lqMJ1j62vrxn1mF3eJzsLa9s9hRofG3AsiYk3UAAABArIrkvqmA0V9lIZcVyCUdja6CiwkPwsV8BfI4CZOyR1Oq7ysvNJWwY0G42dpxN9OP1qz4dum8apG2hqvxVWjkDQ=="} +{ + "authorization_entries": [ + "AAAAAQAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0ukF3TAM31sskAAAAAAAAAAEAAAAAAAAAAahbWC88MmIccL/85Jd6Myxjq5sHF+2dnq1+2H8osKvEAAAAD3dlYl9hdXRoX3ZlcmlmeQAAAAAHAAAAEgAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0ugAAAA4AAAADMTIzAAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAAEAAAABAAAADgAAAAoxODMzNjI2NDkyAAAAAAAA" + ], + "server_signatures": [ + "e905d7821cea210b7cd32f17f9d4b7f9578d36a92f77906ef340ef068d8ab30a77c8d6fb7e589dddfdc81f1c97f2a39426d3b18e30bfcbbf8f988c46fc78ae02" + ], + "credentials": [ + "AAAAAQAAAAHw6CVqzY+dCq3myVJBo1kb3nEGE7oO6obmJeUNvYQ0ukF3TAM31sskABIMtwAAABAAAAABAAAAAQAAABEAAAABAAAAAgAAAA8AAAAKcHVibGljX2tleQAAAAAADQAAACCLcZbWB5Tc+LwIlJMazXz6KECPC89cSo589hUfJAOrhwAAAA8AAAAJc2lnbmF0dXJlAAAAAAAADQAAAEBnEuMUqajx4K8Czt/3Re8EnEzKxil4U0GvrC2OLFM0jrmaAn8UH3MnKM31cS1/+st7gCZjfvgNMRv94DPacCAD" + ] +} ``` -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 @@ -411,7 +284,7 @@ Example: ```json { - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJHQTZVSVhYUEVXWUZJTE5VSVdBQzM3WTRRUEVaTVFWREpIREtWV0ZaSjJLQ1dVQklVNUlYWk5EQSIsImp0aSI6IjE0NGQzNjdiY2IwZTcyY2FiZmRiZGU2MGVhZTBhZDczM2NjNjVkMmE2NTg3MDgzZGFiM2Q2MTZmODg1MTkwMjQiLCJpc3MiOiJodHRwczovL2ZsYXBweS1iaXJkLWRhcHAuZmlyZWJhc2VhcHAuY29tLyIsImlhdCI6MTUzNDI1Nzk5NCwiZXhwIjoxNTM0MzQ0Mzk0fQ.8nbB83Z6vGBgC1X9r3N6oQCFTBzDiITAfCJasRft0z0" + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJsb2NhbGhvc3Q6ODA4MCIsInN1YiI6IkNEWU9RSkxLWldIWjJDVk40M0VWRVFORExFTjU0NElHQ081QTUyVUc0WVM2S0RONVFRMkxVV0tZOjEyMyIsImlhdCI6MTczMjc0MzUwNCwiZXhwIjoxNzMyNzQzODA0LCJqdGkiOiI5NDhhMjNlYTBiMWYyMzYyYWRjNDk2MWJjMWIwOTg4ODEzNTFlNzUzMTdmYTA1YzZmNzc4MTMyODUzYTAzY2Y1IiwiY2xpZW50X2RvbWFpbiI6bnVsbCwiaG9tZV9kb21haW4iOiJsb2NhbGhvc3Q6ODA4MCJ9.BxPcjxvXHIWeZ24rAxGc9loGt5B9h9JZuvGOlgiEosY" } ``` @@ -426,59 +299,77 @@ 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. +## Web Authentication Contract + +The **Server** must deploy a contract at the `WEB_AUTH_CONTRACT_ID` address specified in the **Server**'s +`stellar.toml`. This contract allows the **Server** to customize authentication logic, including client domain +verification and nonce handling. + +The contract must implement the `web_auth_verify` function with the following signature. Within this function, the +contract should call `require_auth` on the address to ensure the client has authorized the operation. If the Server +supports client domain verification, the contract can optionally require a signature from the `client_domain_address`. +All other arguments are ignored by the contract. + +```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 prevent replay attacks + ) { + 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(); + } + } +} +``` -### Verifying Authority to Move Funds +## Client Recommendations -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. +To prevent unintended side effects in case the web_auth_verify invocation operation is executed on the network, the +**Client** should perform the following checks in addition to ensuring there are no sub-invocations in the authorization +entries: -#### Example +### Verify Invocation Details -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. +For each authorization entry, verify the following: -[SEP-24]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md +- **Contract ID**: Ensure it matches the `WEB_AUTH_CONTRACT_ID` specified in the Server's `stellar.toml`. +- **Function Name**: Confirm that the root invocation function is `web_auth_verify`. +- **Arguments**: Verify that the arguments (such as the client account, home domain, and server domain) match the + expected values and are in the same order as described in the + [Web Authentication Contract](#web-authentication-contract) section. -### Verifying Complete Authority +### Simulate the Transaction -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. +Reconstruct the transaction using the authorization entries and simulate the transaction. By simulating the transaction, +the Client can verify: -### Verifying Being a Signer +- The ledger footprint's `read_write` set contains a single `contract_data` entry where the contract ID is the Client + account and the key is `ledger_key_nonce`. -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. +### Signature Expiration Ledger -### Verifying Accounts that Do Not Exist +When signing the authorization entries, the **Client** should set the signature expiration ledger to the current ledger +plus a reasonable buffer. This ensures the signature remains valid for an appropriate duration while preventing replay +attacks. Setting this to the current ledger plus 1 is a reasonable default. -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 +380,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 @@ -500,23 +391,16 @@ from the provided **Client Domain**, the **Server** should return a `400 Bad Req ### Memos Stellar transaction memos are used for a variety of purposes in the ecosystem, but in the context of this standard memos -attached to challenge transactions distinguish sessions that should be entirely separate, scoped, and detached from each -other. Two users who are authenticated with the same Stellar account but different memos should have the same level of -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. +attached to challenges distinguish sessions that should be entirely separate, scoped, and detached from each other. Two +users who are authenticated with the same Contract account but different memos should have the same level of separation +as two users who are authenticated with different Contract accounts. Typically the sessions are unique users who share a +single Contract 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 Contract 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 +413,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 @@ -544,12 +428,16 @@ endpoint to be sure it's using the correct passphrase and is connecting to the s ## JWT best practices -When generating and validating JWTs it's important to follow best practices. The IETF in the process of producing a set -of best current practices when using JWTs: [IETF JWT BCP]. +When generating and validating JWTs it's important to follow best practices. The IETF is in the process of producing a +set 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