SEP: 0010
Title: Stellar Web Authentication
Author: Sergey Nebolsin <sergey@mobius.network>, Tom Quisel <tom.quisel@gmail.com>
Status: Active
Created: 2018-07-31
Updated: 2018-08-14
Version 1.0.1
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-6.
This protocol is a variation of mutual challenge-response, which uses Stellar transactions to encode challenges and responses.
The authentication flow is as follows:
- The client obtains a unique
challenge
, which is represented as specially formed Stellar transaction - The client signs the transaction using the secret key for the user's Stellar account
- The client submits the signed challenge back to the server using
token
endpoint - If the signature checks out, the server responds with a JWT that represents the user's session
- Any future calls to the server can be authenticated by including the JWT as a parameter
The flow achieves several things:
- Both client and server part can be implemented using well-established Stellar libraries
- The client can verify that the server holds the secret key to a particular account
- The server can verify that the client holds the secret key to their account
- The client is able to prove their identity using a Ledger or other hardware wallet as well as by having direct access to the secret key
- The server can chose its own timeout for the user's session
A web service indicates that it supports user authentication via this protocol by specifying WEB_AUTH_ENDPOINT
in their stellar.toml
file. This is how a wallet knows where to find the authentication server. A web 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)
This endpoint must respond with a Stellar transaction signed by the server 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 they control their account. This approach is compatible with hardware wallets such as Ledger. The client can also verify the server's signature to be sure the challenge is signed by the SIGNING_KEY
from the server's stellar.toml
.
GET <WEB_AUTH_ENDPOINT>
Request Parameters:
Name | Type | Description |
---|---|---|
account |
G... string |
The stellar account that the wallet wishes to authenticate with the server |
Example:
GET https://auth.example.com/?account=GCIBUCGPOHWMMMFPFTDWBSVHQRT4DIBJ7AD6BZJYDITBK2LCVBYW7HUQ
On success the endpoint should return 200 OK
HTTP status code and a JSON object with a transaction
field containing an XDR-encoded Stellar transaction with the following:
- source account set to server's signing account
- invalid sequence number (set to 0) so the transaction cannot be run on the Stellar network
- time bounds:
{min: now(), max: now() + 300 }
(we recommend expiration of 5 minutes to give user time to sign transaction) - operations:
manage_data(source: client_account, key: '<anchor name> auth', value: random_nonce())
- The value of key is not important, but can be the name of the anchor followed by
auth
. It can be at most 64 bytes. - The value must be a 64 byte long base64 encoded cryptographic-quality random string
- The value of key is not important, but can be the name of the anchor followed by
- signature by the web service signing account
Example:
{
"transaction": "AAAAAGjeCRajN67nRkVtYO+lpxax9gvitX9FxhZYGXQvs16hAAAAZAAAAAAAAAAAAAAAAQAAAABbcutKAAAAAFty7HYAAAAAAAAAAQAAAAEAAAAAVIx5odtAgqQ+dp4m4QfWntHbOq0hxRCLGI+2Sm0P6EMAAAAKAAAAC01vYml1cyBhdXRoAAAAAAEAAABAG01TL/Ha0YGVrAF6t0UEKP/0Q/NDUymciQBA/CXzYMVlEx2KcHrq3MkpQ9+9sCbCiOYa7wCtusa1tHKygvZRSwAAAAAAAAABL7NeoQAAAEC9v5jdxReIxoCcCXw90dVsIpXwHXkSHUUthCs98D/zpd6TNPvcMgUsQd6cDHzjNk+/00P8M5bHP4rIpFTm7MwN"
}
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 user.
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 application 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's signing key;
- verify that transaction has time bounds set, and that current time is between the minimum and maximum bounds;
- verify that transaction contains a single Manage Data operation and it's source account is not null;
- verify that transaction envelope has a correct signature by server's signing key;
- verify that transaction envelope has a correct signature by the operation's source account;
- use operations's source account to determine the authenticating client and perform any additional service-specific validations.
Upon successful validation service responds with a session JWT, containing the following claims:
iss
(the principal that issued a token, RFC7519, Section 4.1.1) — the URI of the web service (https://example.com
)sub
(the principal that is the subject of the JWT, RFC7519, Section 4.1.2) — the public key of the authenticating Stellar account (G...
)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, however 24 hours is recommended (1530730493
)jti
(the unique identifier for the JWT, RFC7519, Section 4.1.7) — hex-encoded hash of the challenge transaction (f0e754c6ab988eb5fb2811c2cacc4d3d2aa4334763b8df7285ef341921e2530a
)
POST <WEB_AUTH_ENDPOINT>
Request Parameters:
Name | Type | Description |
---|---|---|
transaction |
string | base64 encoded signed challenge transaction XDR |
Example:
POST https://auth.example.com/
Content-Type: application/json
{"transaction": "AAAAAGjeCRajN67nRkVtYO+lpxax9gvitX9FxhZYGXQvs16hAAAAZAAAAAAAAAAAAAAAAQAAAABbcutKAAAAAFty7HYAAAAAAAAAAQAAAAEAAAAAVIx5odtAgqQ+dp4m4QfWntHbOq0hxRCLGI+2Sm0P6EMAAAAKAAAAC01vYml1cyBhdXRoAAAAAAEAAABAG01TL/Ha0YGVrAF6t0UEKP/0Q/NDUymciQBA/CXzYMVlEx2KcHrq3MkpQ9+9sCbCiOYa7wCtusa1tHKygvZRSwAAAAAAAAACL7NeoQAAAEC9v5jdxReIxoCcCXw90dVsIpXwHXkSHUUthCs98D/zpd6TNPvcMgUsQd6cDHzjNk+/00P8M5bHP4rIpFTm7MwNbQ/oQwAAAEAz+6EANIKAAR8G62OkGzbnkxgyYuFwbbCwvbDbkk8db31I/EyR0gRpcxv7zzBAkn8g48N6x1huJhTUO0CPl28M"}
You can examine the example signed challenge transaction in the XDR Viewer.
If the web service 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 a user can use 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"
}