Skip to content

Latest commit

 

History

History
490 lines (383 loc) · 34.2 KB

sep-0045.md

File metadata and controls

490 lines (383 loc) · 34.2 KB

Preamble

SEP: 0045
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: https://github.com/stellar/stellar-protocol/discussions/1620

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 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.

This SEP is based on SEP-10, but does not replace it. This SEP only supports C (contract) accounts. SEP-10 only support G and M accounts. Services wishing to support all accounts should implement both SEPs.

Definitions

  • Address: An identifier representing a possible account on the Stellar network. Addresses begin with specific prefixes like G for public keys (accounts controlled by secret keys), M for muxed accounts and C for contract accounts.
  • Account: A stateful entity on the Stellar network that can hold balances. This can refer to a Stellar account (G... address, or the G... base address of an M... address) or Soroban contract that abstracts an account (C... addresses).

Abstract

This protocol is a variation of mutual challenge-response, which uses Stellar authorization entries to encode challenges and responses.

It involves the following components:

  • A Home Domain: a domain hosting a SEP-1 stellar.toml containing a WEB_AUTH_FOR_CONTRACTS_ENDPOINT (URL), WEB_AUTH_CONTRACT_ID, and SIGNING_KEY (G...).
    • The SIGNING_KEY from this domain is referred to as the Home Domain Address in this document.
  • A Server: a server providing the WEB_AUTH_FOR_CONTRACTS_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 Client Account: the account being authenticated.
    • A Stellar contract account (C...).
  • 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 containing a SIGNING_KEY used for Verifying the Client Domain
    • The SIGNING_KEY from this domain is referred to as the Client Domain Address in this document.
  • 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.
  2. The Client looks up the WEB_AUTH_FOR_CONTRACTS_ENDPOINT, WEB_AUTH_CONTRACT_ID and SIGNING_KEY (i.e. Home Domain Address) from the stellar.toml.

The authentication flow is as follows:

  1. The Client requests a unique challenge from the Server which includes a list of authorization entries XDR-encoded strings
  2. If the request contains a client_domain parameter, the Server may fetch the Client Domain Address and generate the challenge with an additional authorization entry as described in the Response section.
  3. The Server responds with the challenge
  4. The Client verifies that each authorization entry does not contain any sub-invocations
  5. The Client verifies that the contract_address in each authorization entry matches the WEB_AUTH_CONTRACT_ID from the Server's stellar.toml
  6. The Client verifies that the function_name in each authorization entry is web_auth_verify
  7. The Client verifies that the args map in each authorization entry match the expected values and is the same across all authorization entries:
    1. The account value matches the Client Account address
    2. The home_domain value matches the Home Domain
    3. The home_domain_address value matches the Home Domain Address
    4. The web_auth_domain value matches the Server's domain
    5. If the Client included a client_domain in the request:
      1. The client_domain value matches the Client's domain
      2. The client_domain_address value matches the Client Domain Address
  8. The Client verifies that there is an authorization entry where credentials.address.address is the Home Domain Address and contains a valid signature from the Home Domain Address
  9. The Client signs the authorization entry where credentials.address.address is the Client Account using the secret key(s) of the signer(s) for the Client Account
    • Note: Client signatures may not be required if the contract's __check_auth implementation does not require them
  10. The Client obtains a signature from the Client Domain Address for the authorization entry where credentials.address.address is the Client Domain Address if the Client included a client domain in the request
  11. The Client simulates the transaction with the signed authorization entries and verifies the following to ensure the transaction does not have any unintended side effects:
    • The transaction's ledger footprint read_write set contains only contract_data entries where:
      • The contract is the Client Account address, and the key is ledger_key_nonce.
      • The contract is the Home Domain Address, and the key is ledger_key_nonce.
      • (Optional) if an authorization entry for the Client Domain Address was present in the challenge, the contract is the Client Domain Address, and the key is ledger_key_nonce.
  12. The Client submits the signed authorization entries back to the Server using token endpoint
  13. The Server extracts the arguments from the authorization entries returned by the client
  14. The Server verifies that the contract_address in each authorization entry matches the WEB_AUTH_CONTRACT_ID from the Server's stellar.toml
  15. The Server verifies that the function_name in each authorization entry is web_auth_verify
  16. The Server verifies that the args map in each authorization entry match the expected values and are the same across all authorization entries:
    1. The account value matches the Client Account address
    2. The home_domain value matches the Home Domain
    3. The home_domain_address value matches the Home Domain Address
    4. The web_auth_domain value matches the Server's domain
    5. The client_domain_address value matches the Client Domain Address if the Client included a client_domain in the request, otherwise it is not present
  17. (Optional) The Server verifies that the nonce argument is the same across all authorization entries and is unique
  18. The Server verifies that there is an authorization entry where credentials.address.address is the Home Domain Address and contains a valid signature from the Home Domain Address
  19. The Server verifies that there is an authorization entry where credentials.address.address is the Client Account address
  20. The Server verifies that there is an authorization entry where credentials.address.address is the Client Domain Address if the arguments included a client_domain_address
  21. The Server constructs a transaction with a single Invoke Host Function operation where the contract address is WEB_AUTH_CONTRACT_ID and the function is web_auth_verify using the previously extracted arguments and the authorization entries returned by the client
  22. If the simulation succeeds, the Server responds with a JWT 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 Home Domain Address
  • The Server can verify that the Client is authorized by the contract account to create an authenticated session
  • The Server can choose its own timeout for the authenticated session
  • The Server can choose to include other application-specific claims

Authentication Endpoint

The organization with a Home Domain indicates that it supports authentication via this protocol by specifying WEB_AUTH_FOR_CONTRACTS_ENDPOINT in their stellar.toml 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:

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, the preflight request (OPTIONS request) must be implemented in all the endpoints that support Cross-Origin.

Challenge

This endpoint must respond with authorization entries to be signed by the Client. The Client signs the entries using standard Stellar libraries and submit it to 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 Home Domain Address, 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 <WEB_AUTH_FOR_CONTRACTS_ENDPOINT>
Request Parameters:
Name Type Description
account C... string The Client Account address (C...) that the Client wishes to authenticate with the Server.
home_domain string A Home Domain. Servers that generate tokens for multiple Home Domains can use this parameter to identify which home domain the Client hopes to authenticate with.
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. 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=CCIBUCGPOHWMMMFPFTDWBSVHQRT4DIBJ7AD6BZJYDITBK2LCVBYW7HUQ&home_domain=example.com

Response

Success

On success the endpoint must return 200 OK HTTP status code and a JSON object with the following fields:

  • authorization_entries: an XDR-encoded array of SorobanAuthorizationEntrys. It contains an entry for the Client Account in addition to a signed entry from the Home Domain Address and optionally the Client Domain Address. The entries are not guaranteed to be in any specific order.

Each entry's root_invocation function is a contract_fn with no sub-invocations and the following values:

  • contract_address is the WEB_AUTH_CONTRACT_ID from the Server's stellar.toml
  • function_name is web_auth_verify
  • An args map containing the following Symbol to String pairs:
    • account matches the account from the request
    • home_domain matches the home_domain from the request
    • home_domain_address matches the Home Domain Address
    • web_auth_domain matches the Server's domain
    • client_domain matches the Client's domain if the Client included a client_domain in the request and the server supports Verifying the Client Domain
    • client_domain_address matches the Client Domain Address if the Client included a client_domain in the request and the server supports Verifying the Client Domain
    • (Optional) nonce is a unique value that is the same across all authorization entries
  • 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:

{
  "authorization_entries": "AAAAAQAAAAHDwFN8u4knndhlRcZmGC28sL8G7WJnadYNY88ZwAICiAhjuHNQ4DVQAAAAAAAAAAEAAAAAAAAAAX6lC8GUFNGjTHdg8uQyeJvi1taYAbI3H1Ss91/oUNawAAAAD3dlYl9hdXRoX3ZlcmlmeQAAAAABAAAAEQAAAAEAAAAFAAAADwAAAAdhY2NvdW50AAAAAA4AAAA4Q0RCNEFVMzRYT0VTUEhPWU1WQzRNWlFZRlc2TEJQWUc1VlJHTzJPV0JWUjQ2R09BQUlCSVE0R0QAAAAPAAAAC2hvbWVfZG9tYWluAAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAA8AAAATaG9tZV9kb21haW5fYWRkcmVzcwAAAAAOAAAAOEdESkxCWVlLTUNYTlZWTkFCT0U2Nk5ZWFFHSUE1QUM1RDIyM1oyS0Y2WkVZSzRVQkNBN0ZLTFRHAAAADwAAAAVub25jZQAAAAAAAA4AAAAKMjA2MDIxNDExNQAAAAAADwAAAA93ZWJfYXV0aF9kb21haW4AAAAADgAAAA5sb2NhbGhvc3Q6ODA4MAAAAAAAAAAAAAEAAAAAAAAAANKw4wpgrtrVoAuJ7zcXgZAOgF0etbzpRfZJhXKBED5VHSLrhXQ/QVwAAaLlAAAAEAAAAAEAAAABAAAAEQAAAAEAAAACAAAADwAAAApwdWJsaWNfa2V5AAAAAAANAAAAINKw4wpgrtrVoAuJ7zcXgZAOgF0etbzpRfZJhXKBED5VAAAADwAAAAlzaWduYXR1cmUAAAAAAAANAAAAQDUYC2mj/RW0NcGfY66p+eltjwflRSmwc8ZZ7as2HAUvu4k/bY2ZHIhhS6M7Ufx74XmV9QlxHPv6vXUA/j9uRAwAAAAAAAAAAX6lC8GUFNGjTHdg8uQyeJvi1taYAbI3H1Ss91/oUNawAAAAD3dlYl9hdXRoX3ZlcmlmeQAAAAABAAAAEQAAAAEAAAAFAAAADwAAAAdhY2NvdW50AAAAAA4AAAA4Q0RCNEFVMzRYT0VTUEhPWU1WQzRNWlFZRlc2TEJQWUc1VlJHTzJPV0JWUjQ2R09BQUlCSVE0R0QAAAAPAAAAC2hvbWVfZG9tYWluAAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAA8AAAATaG9tZV9kb21haW5fYWRkcmVzcwAAAAAOAAAAOEdESkxCWVlLTUNYTlZWTkFCT0U2Nk5ZWFFHSUE1QUM1RDIyM1oyS0Y2WkVZSzRVQkNBN0ZLTFRHAAAADwAAAAVub25jZQAAAAAAAA4AAAAKMjA2MDIxNDExNQAAAAAADwAAAA93ZWJfYXV0aF9kb21haW4AAAAADgAAAA5sb2NhbGhvc3Q6ODA4MAAAAAAAAA==",
  "network_passphrase": "Test SDF Network ; September 2015"
}

You can examine the example challenge in the XDR Viewer

Error

Every other HTTP status code will be considered an error. For example:

{
  "error": "The provided account has requested too many challenges recently. Try again later."
}

Token

This endpoint accepts a signed challenge transaction, validates it and responds with a session JSON Web Token authenticating the account.

The Client submits a challenge (that was previously returned by the challenge endpoint) as a HTTP POST request to WEB_AUTH_FOR_CONTRACTS_ENDPOINT using one of the following formats (both should be equally supported by the server):

  • Content-Type: application/x-www-form-urlencoded, body: authorization_entries=<XDR (URL-encoded)>)
  • Content-Type: application/json, body: {"authorization_entries": "<XDR>""}

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.

  1. Extract the arguments from the authorization entries returned by the client;
  2. Verify that the contract_address in each authorization entry matches the WEB_AUTH_CONTRACT_ID from the Server's stellar.toml;
  3. Verify that the function_name in each authorization entry is web_auth_verify;
  4. Verify that the args in each authorization entry match the expected values and is the same across all authorization entries:
    1. The home_domain value matches the Home Domain;
    2. The home_domain_address value matches the Home Domain Address;
    3. The web_auth_domain value matches the Server's domain;
    4. The client_domain is present if client_domain_address is present;
    5. The client_domain_address value matches the Client Domain Address if client_domain is present;
  5. (Optional) Verify that the nonce argument is the same across all authorization entries and is valid;
  6. Verify that there is an authorization entry where credentials.address.address is the Home Domain Address and contains a valid signature from the Home Domain Address;
  7. Verify that there is an authorization entry where credentials.address.address is the Client Account address
  8. Verify that there is an authorization entry where credentials.address.address is the Client Domain Address if the arguments included a client_domain_address.
  9. Construct a transaction with a single Invoke Host Function operation where the contract address is WEB_AUTH_CONTRACT_ID and the function is web_auth_verify using the previously extracted arguments and the authorization entries returned by the client;
  10. Simulate the transaction and verify that it succeeds.

The verification process confirms that the Client controls the Client Account and that the challenge was not tampered with. Depending on your application this may mean complete signing authority, some threshold of control, or being a signer of the account. See 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) — 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) — the Client Account's address (C...).
  • iat (the time at which the JWT was issued 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) — 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)

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.

Request

POST <WEB_AUTH_ENDPOINT>

Request Parameters:

Name Type Description
authorization_entries string an XDR-encoded array of signed SorobanAuthorizationEntrys

Example:

POST https://auth.example.com/
Content-Type: application/json

{
  "authorization_entries": "AAAAAQAAAAHDwFN8u4knndhlRcZmGC28sL8G7WJnadYNY88ZwAICiAhjuHNQ4DVQAAGi7gAAABAAAAABAAAAAQAAABEAAAABAAAAAgAAAA8AAAAKcHVibGljX2tleQAAAAAADQAAACCLcZbWB5Tc+LwIlJMazXz6KECPC89cSo589hUfJAOrhwAAAA8AAAAJc2lnbmF0dXJlAAAAAAAADQAAAEDIHVoFpnEUUmqOzTCzsYqu3naPvxUh1BmreIjoBbgXcbsCulvxQQdWKq40JWxNuacxOnoiOy1qyRD2ylTbirgBAAAAAAAAAAF+pQvBlBTRo0x3YPLkMnib4tbWmAGyNx9UrPdf6FDWsAAAAA93ZWJfYXV0aF92ZXJpZnkAAAAAAQAAABEAAAABAAAABQAAAA8AAAAHYWNjb3VudAAAAAAOAAAAOENEQjRBVTM0WE9FU1BIT1lNVkM0TVpRWUZXNkxCUFlHNVZSR08yT1dCVlI0NkdPQUFJQklRNEdEAAAADwAAAAtob21lX2RvbWFpbgAAAAAOAAAADmxvY2FsaG9zdDo4MDgwAAAAAAAPAAAAE2hvbWVfZG9tYWluX2FkZHJlc3MAAAAADgAAADhHREpMQllZS01DWE5WVk5BQk9FNjZOWVhRR0lBNUFDNUQyMjNaMktGNlpFWUs0VUJDQTdGS0xURwAAAA8AAAAFbm9uY2UAAAAAAAAOAAAACjIwNjAyMTQxMTUAAAAAAA8AAAAPd2ViX2F1dGhfZG9tYWluAAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAAAAAAABAAAAAAAAAADSsOMKYK7a1aALie83F4GQDoBdHrW86UX2SYVygRA+VR0i64V0P0FcAAGi5QAAABAAAAABAAAAAQAAABEAAAABAAAAAgAAAA8AAAAKcHVibGljX2tleQAAAAAADQAAACDSsOMKYK7a1aALie83F4GQDoBdHrW86UX2SYVygRA+VQAAAA8AAAAJc2lnbmF0dXJlAAAAAAAADQAAAEA1GAtpo/0VtDXBn2OuqfnpbY8H5UUpsHPGWe2rNhwFL7uJP22NmRyIYUujO1H8e+F5lfUJcRz7+r11AP4/bkQMAAAAAAAAAAF+pQvBlBTRo0x3YPLkMnib4tbWmAGyNx9UrPdf6FDWsAAAAA93ZWJfYXV0aF92ZXJpZnkAAAAAAQAAABEAAAABAAAABQAAAA8AAAAHYWNjb3VudAAAAAAOAAAAOENEQjRBVTM0WE9FU1BIT1lNVkM0TVpRWUZXNkxCUFlHNVZSR08yT1dCVlI0NkdPQUFJQklRNEdEAAAADwAAAAtob21lX2RvbWFpbgAAAAAOAAAADmxvY2FsaG9zdDo4MDgwAAAAAAAPAAAAE2hvbWVfZG9tYWluX2FkZHJlc3MAAAAADgAAADhHREpMQllZS01DWE5WVk5BQk9FNjZOWVhRR0lBNUFDNUQyMjNaMktGNlpFWUs0VUJDQTdGS0xURwAAAA8AAAAFbm9uY2UAAAAAAAAOAAAACjIwNjAyMTQxMTUAAAAAAA8AAAAPd2ViX2F1dGhfZG9tYWluAAAAAA4AAAAObG9jYWxob3N0OjgwODAAAAAAAAA="
}

You can examine the example credentials in the XDR Viewer

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:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJsb2NhbGhvc3Q6ODA4MCIsInN1YiI6IkNDTEhCVVJZTzRCMkpGVTRZQlpVUVpLSlEyWjM3MjNEUFhUV1U2WURQWE40VFozS0hWUTdOT1VMIiwiaWF0IjoxNzM3MTQyMjI0LCJleHAiOjE3MzcxNDI1MjQsImp0aSI6IjI2MWQ3ZjAyMzljOTMxMDVmMmU1NGJjMWZkYzk2OGY5ZTY4MTUyOTQ1MjljMjQwZDgxMzgwY2U3MGQyYzUyMjAiLCJob21lX2RvbWFpbiI6ImxvY2FsaG9zdDo4MDgwIn0.WrtL94Erny9DpfXehA0dfnpmNvDPPuh3_0oxAT16xuw"
}

Check the example session token on JWT.IO.

Every other HTTP status code will be considered an error. For example:

{
  "error": "The provided authorization entries are not valid"
}

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, and call require_auth on the home domain address so that the Server can verify that the challenge has not been tampered.

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.

The server can optionally include a nonce in the web_auth_verify function arguments. The nonce is a random string generated by the Server and included in the challenge transaction. The nonce is verified when the Client submits signed challenge to the Server. The nonce is used to prevent replay attacks.

Below is an example implementation of the web_auth_verify function in Rust:

#[contracterror]
pub enum WebAuthError {
    MissingArgument = 1,
}

#[contractimpl]
impl WebAuthContract {
    pub fn __constructor(env: Env, admin: Address) -> () {
        env.storage().instance().set(&DataKey::Admin, &admin);
    }

    /// Verifies the client is authorized to authenticate with the server
    ///
    /// Arguments:
    /// - account: The client account address
    /// - home_domain_address: The home domain address
    /// - web_auth_domain: The server's domain
    /// - client_domain: The client domain (optional)
    /// - client_domain_address: The client domain address (optional)
    /// - nonce: A random string generated by the server to prevent replay attacks (optional)
    pub fn web_auth_verify(env: Env, args: Map<Symbol, String>) -> Result<(), WebAuthError> {
        if let Some(address) = args.get(Symbol::new(&env, "account")) {
            let addr = Address::from_string(&address);
            addr.require_auth();
        } else {
            return Err(WebAuthError::MissingArgument);
        }

        if let Some(home_domain_address) = args.get(Symbol::new(&env, "home_domain_address")) {
            let home_domain_addr = Address::from_string(&home_domain_address);
            home_domain_addr.require_auth();
        } else {
            return Err(WebAuthError::MissingArgument);
        }

        // Optional client domain verification
        if let Some(client_domain_address) = args.get(Symbol::new(&env, "client_domain_address")) {
            let client_domain_addr = Address::from_string(&client_domain_address);
            client_domain_addr.require_auth();
        }

        Ok(())
    }
}

Client Recommendations

To prevent unintended side effects in case the web_auth_verify invocation operation is executed on the network, the Client should consider implementing the following recommendations in addition to verifying the function arguments and sub-invocations.

Signature Expiration Ledger

When signing the authorization entries, the Client should set the signature expiration ledger to the current ledger plus a 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.

Verification

Verifying the Client Domain

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 SEP-1 stellar.toml file containing a SIGNING_KEY attribute (i.e. the Client Domain Address). 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 by the Client Domain Address, proving that the Client is associated with the Client Domain. Web services requiring 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 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.

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-45 clients and servers must use the following convention when deciding what network passphrase to use for signing and verifying 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-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 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 is in the process of producing a set of best current practices when using JWTs: IETF JWT BCP.

Implementations

  • None

Changelog

  • 0.1.0: Initial draft