diff --git a/ecosystem/sep-0045.md b/ecosystem/sep-0045.md new file mode 100644 index 000000000..a0c1b2825 --- /dev/null +++ b/ecosystem/sep-0045.md @@ -0,0 +1,555 @@ +## Preamble + +``` +SEP: 0045 +Title: Stellar Web Authentication +Author: +Status: Draft +Created: 2024-10-08 +Updated: 2024-10-08 +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 +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. + +## Abstract + +This protocol is a variation of mutual challenge-response, which uses Stellar transactions 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. + - 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** + +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`. + +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 + session + +The flow achieves several things: + +- Both **Client** and **Server** can be implemented using well-established Stellar libraries +- 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 +**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) + +## Cross-Origin Headers + +Valid CORS headers are necessary to allow web clients from other sites to use the endpoints. The following HTTP header +must be set for all authentication endpoints, including error responses. + +``` +Access-Control-Allow-Origin: * +``` + +In order for browsers-based wallets to validate the CORS headers, as +[specified by W3C](https://www.w3.org/TR/cors/#preflight-request), the preflight request (OPTIONS request) must be +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. + +#### 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" +} +``` + +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. | + +Example: + +``` +GET https://auth.example.com/?account=GCIBUCGPOHWMMMFPFTDWBSVHQRT4DIBJ7AD6BZJYDITBK2LCVBYW7HUQ +``` + +#### Response + +##### Success + +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** +- `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==", + "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) + +##### Error + +Every other HTTP status code will be considered an error. For example: + +```json +{ + "error": "The provided account has requested too many challenges recently. Try again later." +} +``` + +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): + +- Content-Type: `application/x-www-form-urlencoded`, body: `transaction=`) +- Content-Type: `application/json`, body: `{"transaction": ""}` + +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; + +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 +[Verification](#verification) for examples. + +Upon successful verification, **Server** responds with a session JWT, containing the following claims: + +- `iss` (the principal that issued a token, [RFC7519, Section 4.1.1](https://tools.ietf.org/html/rfc7519#section-4.1.1)) + — 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...`). +- `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]. + +The **Server** should not provide more than one JWT for a specific challenge transaction. + +[Uniform Resource Identifier (URI)]: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier +[RFC7519]: https://tools.ietf.org/html/rfc7519 + +#### Request + +``` +POST +``` + +Request Parameters: + +| Name | Type | Description | +| ------------- | ------ | --------------------------------------------------- | +| `transaction` | string | the base64 encoded signed challenge transaction XDR | + +Example: + +``` +POST https://auth.example.com/ +Content-Type: application/json + +{"transaction": "AAAAAgAAAADIiRu2BrqqeOcP28PWCkD4D5Rjjsqh71HwvqFX+F4VXAAAAGQAAAAAAAAAAAAAAAEAAAAAXzrUcQAAAABfOtf1AAAAAAAAAAEAAAABAAAAAEEB8rhqNa70RYjaNnF1ARE2CbL50iR9HPXST/fImJN1AAAACgAAADB0aGlzaXNhdGVzdC5zYW5kYm94LmFuY2hvci5hbmNob3Jkb21haW4uY29tIGF1dGgAAAABAAAAQGdGOFlIQm1zaGpEWEY0L0VJUFZucGVlRkxVTDY2V0tKMVBPYXZuUVVBNjBoL09XaC91M2Vvdk54WFJtSTAvQ2UAAAAAAAAAAvheFVwAAABAheKE1HjGnUCNwPbX8mz7CqotShKbA+xM2Hbjl6X0TBpEprVOUVjA6lqMJ1j62vrxn1mF3eJzsLa9s9hRofG3AsiYk3UAAABArIrkvqmA0V9lIZcVyCUdja6CiwkPwsV8BfI4CZOyR1Oq7ysvNJWwY0G42dpxN9OP1qz4dum8apG2hqvxVWjkDQ=="} +``` + +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) + +#### Response + +If the **Server** successfully validates the submitted challenge transaction, the endpoint should return `200 OK` HTTP +status code and a JSON object with the following fields: + +| Name | Type | Description | +| ------- | ------ | ------------------------------------------------------------------------------ | +| `token` | string | The JWT that can be used to authenticate future endpoint calls with the anchor | + +Example: + +```json +{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJHQTZVSVhYUEVXWUZJTE5VSVdBQzM3WTRRUEVaTVFWREpIREtWV0ZaSjJLQ1dVQklVNUlYWk5EQSIsImp0aSI6IjE0NGQzNjdiY2IwZTcyY2FiZmRiZGU2MGVhZTBhZDczM2NjNjVkMmE2NTg3MDgzZGFiM2Q2MTZmODg1MTkwMjQiLCJpc3MiOiJodHRwczovL2ZsYXBweS1iaXJkLWRhcHAuZmlyZWJhc2VhcHAuY29tLyIsImlhdCI6MTUzNDI1Nzk5NCwiZXhwIjoxNTM0MzQ0Mzk0fQ.8nbB83Z6vGBgC1X9r3N6oQCFTBzDiITAfCJasRft0z0" +} +``` + +Check the example session token on +[JWT.IO](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJHQTZVSVhYUEVXWUZJTE5VSVdBQzM3WTRRUEVaTVFWREpIREtWV0ZaSjJLQ1dVQklVNUlYWk5EQSIsImp0aSI6IjE0NGQzNjdiY2IwZTcyY2FiZmRiZGU2MGVhZTBhZDczM2NjNjVkMmE2NTg3MDgzZGFiM2Q2MTZmODg1MTkwMjQiLCJpc3MiOiJodHRwczovL2ZsYXBweS1iaXJkLWRhcHAuZmlyZWJhc2VhcHAuY29tLyIsImlhdCI6MTUzNDI1Nzk5NCwiZXhwIjoxNTM0MzQ0Mzk0fQ.8nbB83Z6vGBgC1X9r3N6oQCFTBzDiITAfCJasRft0z0). + +Every other HTTP status code will be considered an error. For example: + +```json +{ + "error": "The provided transaction is not valid" +} +``` + +## 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 + +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. + +### Verifying the Client Domain + +A web service requiring SEP-10 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 +[SEP-1 stellar.toml](sep-0001.md) file containing a `SIGNING_KEY` attribute (i.e. the **Client Domain Account** on the +**Client Domain**). The `SIGNING_KEY` attribute must be a Stellar public key in the form of a `G` address. The secret +key paired with the `SIGNING_KEY` should be protected as anyone in possession of the secret can verify the **Client +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 +challenge. + +**Servers** may chose which **Client Domains** to verify. If the **Client** requests verification of its domain but the +**Server** has no use for verifying that domain, the **Server** should proceed as if the **Client** did not provide the +domain in the request for the challenge transaction. If the **Server** attempts but is unable to fetch the `SIGNING_KEY` +from the provided **Client Domain**, the **Server** should return a `400 Bad Request` HTTP status code. + +### 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. + +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. + +## JWT Expiration + +Servers should select an expiration time for the JWT that is appropriate for the assumptions and risk of the +interactions the **Client** can perform with it. A **Client** may be in control of an account at the time the JWT is +issued but they may lose control of the account through a change in signers. Expiration times that are too long increase +the risk that control on the account has changed. Expiration times that are too short increase the number of times +authentication must reoccur, and a user using a hardware signing device or who must complete a complex signing process +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 +servers must use the following convention when deciding what network passphrase to use for signing and verifying +signatures in SEP-10: + +- 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 +with the Stellar network. + +The client can examine the `network_passphrase` (if defined) that the server includes in its response from the challenge +endpoint to be sure it's using the correct passphrase and is connecting to the server that it expected. + +## 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]. + +[IETF JWT BCP]: https://tools.ietf.org/wg/oauth/draft-ietf-oauth-jwt-bcp/ +[SEP-1]: sep-0001.md + +## Changelog + +- `0.1.0`: Initial draft