SEP: 0010
Title: Stellar Web Authentication
Author: Sergey Nebolsin <@nebolsin>, Tom Quisel <[email protected]>, Leigh McCulloch <@leighmcculloch>, Jake Urban <[email protected]>
Status: Active
Created: 2018-07-31
Updated: 2022-04-22
Version: 3.3.2
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.
This SEP also supports authenticating users of shared, omnibus, or pooled Stellar accounts. Clients can use memos or muxed accounts to distinguish users or sub-accounts of shared accounts.
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 containing a
WEB_AUTH_ENDPOINT
(URL) andSIGNING_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.
- The
- 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 Stellar 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 containing a
SIGNING_KEY
used for Verifying the Client Domain- The
SIGNING_KEY
from this domain is the Client Domain Account
- The
The discovery flow is as follows:
- The Client retrieves the
stellar.toml
from the Home Domain in accordance with SEP-1 stellar.toml. - The Client looks up the
WEB_AUTH_ENDPOINT
andSIGNING_KEY
(i.e. Server Account) from thestellar.toml
.
The authentication flow is as follows:
- The Client requests a unique
challenge
transaction from the Server, which is represented as specially formed Stellar transaction - 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 section. - The Server responds with the challenge transaction.
- The Client verifies that the transaction has an invalid sequence number 0. This is extremely important to ensure the transaction isn't malicious.
- The Client verifies that the transaction is signed by the Server Account obtained through discovery flow.
- The Client verifies that the transaction's first operation is a Manage Data operation that has its:
- Source account set to the Client Account
- Key set to
<home domain> auth
where the home domain is the Home Domain. - Value set to a nonce value.
- The Client verifies that if the transaction has a Manage Data operation with key
web_auth_domain
that it has:- Source account set to the Server Account.
- Value set to the Server's domain that the client requested the challenge from.
- The Client verifies that if the transaction has other operations they are Manage Data operations and that their source account is set to:
- The Client Domain Account if the Manage Data operation key is set to
client_domain
- Otherwise, the Server Account.
- The Client Domain Account if the Manage Data operation key is set to
- 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 - The Client signs the transaction using the secret key(s) of the signer(s) for the Client Account
- The Client submits the signed challenge back to the Server using
token
endpoint - The Server checks that the Client Account exists
- If the Client Account exists:
- The Server gets the signers of the Client Account
- The Server verifies that one or more signatures are from signers of the Client Account.
- The Server verifies that there is only one additional signature from the Server Account
- The Server verifies the weight provided by the signers of the Client Account meets the required threshold(s), if any
- If the Client Account does not exist (optional):
- The Server verifies the signature count is two
- The Server verifies that one signature is correct for the master key of the Client Account
- The Server verified that the other signature is from the Server Account
- 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 additionalclient_domain
claim in the JWT included in the response - If the signatures check out, 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 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
The organization with a Home Domain indicates that it supports authentication via this protocol by specifying WEB_AUTH_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:
GET <WEB_AUTH_ENDPOINT>
: request a challenge (step 1)POST <WEB_AUTH_ENDPOINT>
: exchange a signed challenge for session JWT (step 2)
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.
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
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.
GET <WEB_AUTH_ENDPOINT>
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 section for details. |
home_domain |
string | (optional) 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. 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. 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
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: '<home domain> 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 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:
{
"transaction": "AAAAAgAAAADIiRu2BrqqeOcP28PWCkD4D5Rjjsqh71HwvqFX+F4VXAAAAGQAAAAAAAAAAAAAAAEAAAAAXzrUcQAAAABfOtf1AAAAAAAAAAEAAAABAAAAAEEB8rhqNa70RYjaNnF1ARE2CbL50iR9HPXST/fImJN1AAAACgAAADB0aGlzaXNhdGVzdC5zYW5kYm94LmFuY2hvci5hbmNob3Jkb21haW4uY29tIGF1dGgAAAABAAAAQGdGOFlIQm1zaGpEWEY0L0VJUFZucGVlRkxVTDY2V0tKMVBPYXZuUVVBNjBoL09XaC91M2Vvdk54WFJtSTAvQ2UAAAAAAAAAAfheFVwAAABAheKE1HjGnUCNwPbX8mz7CqotShKbA+xM2Hbjl6X0TBpEprVOUVjA6lqMJ1j62vrxn1mF3eJzsLa9s9hRofG3Ag==",
"network_passphrase": "Public Global Stellar Network ; September 2015"
}
You can examine the example challenge transaction in the XDR Viewer
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."
}
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 transaction (that was previously returned by the 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=<signed XDR (URL-encoded)>
) - Content-Type:
application/json
, body:{"transaction": "<signed 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.
- 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 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
orhttps://example.com/G...
)sub
(the principal that is the subject of the JWT, RFC7519, Section 4.1.2) — there are several possible formats:- If the Client Account is a muxed account (
M...
), thesub
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...
).
- And, a memo was attached to the challenge transaction, the
- If the Client Account is a muxed account (
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 aclient_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.
POST <WEB_AUTH_ENDPOINT>
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
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.eyJzdWIiOiJHQTZVSVhYUEVXWUZJTE5VSVdBQzM3WTRRUEVaTVFWREpIREtWV0ZaSjJLQ1dVQklVNUlYWk5EQSIsImp0aSI6IjE0NGQzNjdiY2IwZTcyY2FiZmRiZGU2MGVhZTBhZDczM2NjNjVkMmE2NTg3MDgzZGFiM2Q2MTZmODg1MTkwMjQiLCJpc3MiOiJodHRwczovL2ZsYXBweS1iaXJkLWRhcHAuZmlyZWJhc2VhcHAuY29tLyIsImlhdCI6MTUzNDI1Nzk5NCwiZXhwIjoxNTM0MzQ0Mzk0fQ.8nbB83Z6vGBgC1X9r3N6oQCFTBzDiITAfCJasRft0z0"
}
Check the example session token on JWT.IO.
Every other HTTP status code will be considered an error. For example:
{
"error": "The provided transaction is not valid"
}
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.
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.
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.
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.
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.
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.
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 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.
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 <WEB_AUTH_ENDPOINT>
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 (:
).
Conceptually, a Stellar account and memo (of type id
) is equivalent to a muxed account (M...
) defined in the protocol by CAP-0027 and standardized for use in the ecosystem in SEP-0023. 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.
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.
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.
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.
- iOS and macOS SDK: https://github.com/Soneso/stellar-ios-mac-sdk/blob/master/README.md#8-stellar-web-authentication
- Flutter SDK: https://github.com/Soneso/stellar_flutter_sdk/blob/master/documentation/sdk_examples/sep-0010-webauth.md
- PHP SDK: https://github.com/Soneso/stellar-php-sdk/blob/main/examples/sep-0010-webauth.md
v3.3.2
: Fixed formatting of markdown section headers. (1175)