SSeev PlusDocs
Stellar Anchor

SEP-10: Authentication

Authenticate your Stellar account with the SeevCash anchor using the SEP-10 web authentication protocol.

SEP-10 is a mutual authentication protocol between a Stellar wallet and an anchor. It proves that you control a specific Stellar account without exposing your secret key. On success, the anchor issues a JWT token used to authorize all subsequent SEP-6, SEP-12, and SEP-38 requests.

Flow overview

Client                                  Anchor
  │                                       │
  │  GET /auth?account={publicKey}        │
  │──────────────────────────────────────►│
  │                                       │
  │  Challenge transaction (XDR)          │
  │◄──────────────────────────────────────│
  │                                       │
  │  Sign challenge with secret key       │
  │                                       │
  │  POST /auth (signed XDR)              │
  │──────────────────────────────────────►│
  │                                       │
  │  JWT token                            │
  │◄──────────────────────────────────────│

Endpoints

MethodPathDescription
GET/authRequest a challenge transaction
POST/authSubmit the signed challenge and receive a JWT

Step 1: Request a challenge

curl -X GET "https://<your-anchor-domain>/auth?account=GCEXAMPLE4KEYPAIR7HERE2REPLACE5WITH5YOUR5ACTUAL5STELLAR5KEY"
import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

// Setup
const wallet = Wallet.TestNet();
const ANCHOR_HOME_DOMAIN = '<your-anchor-domain>';
const anchor = wallet.anchor({ homeDomain: ANCHOR_HOME_DOMAIN });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

// Request and sign the challenge in one step
const sep10 = await anchor.sep10();
const authToken = await sep10.authenticate({ accountKp });

Response (200):

{
  "transaction": "AAAAAgAAAADHPbSgR...base64_xdr_challenge...==",
  "network_passphrase": "Test SDF Network ; September 2015"
}

The transaction field contains a base64-encoded Stellar transaction XDR. This transaction is not submitted to the network — it's a cryptographic challenge.

Step 2: Sign and submit the challenge

Sign the challenge transaction with the secret key corresponding to your public key, then POST the signed XDR back to the anchor.

curl -X POST "https://<your-anchor-domain>/auth" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction": "AAAAAgAAAADHPbSgR...signed_base64_xdr...=="
  }'
import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

// The wallet SDK handles challenge request, signing, and submission internally.
// sep10.authenticate() performs all three steps:
// 1. GET /auth?account=... (request challenge)
// 2. Sign the challenge XDR with accountKp
// 3. POST /auth (submit signed challenge)
// Returns the JWT token directly.

const wallet = Wallet.TestNet();
const anchor = wallet.anchor({ homeDomain: '<your-anchor-domain>' });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

const sep10 = await anchor.sep10();
const authToken = await sep10.authenticate({ accountKp });
console.log("SEP-10 JWT:", authToken);

Response (200):

{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FuY2hvci1wcm9kLnNlZXZjYXNoLmNvbSIsInN1YiI6IkdDRVhBTVBMRTRLRVlQQUlSN0hFUkUyUkVQTEFDRTVXSVRINVlPVVI1QUNUVUFMNVNURU..."
}

The JWT token typically expires after 24 hours. Cache it and refresh before expiry. All subsequent API calls must include it as a Bearer token.

TypeScript example

import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

const wallet = Wallet.TestNet();
const ANCHOR_HOME_DOMAIN = '<your-anchor-domain>';
const anchor = wallet.anchor({ homeDomain: ANCHOR_HOME_DOMAIN });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

async function authenticate(): Promise<string> {
  const sep10 = await anchor.sep10();
  const authToken = await sep10.authenticate({ accountKp });
  return authToken;
}

// Usage
const jwt = await authenticate();
console.log("SEP-10 JWT:", jwt);

Using the token

Include the JWT in all subsequent requests to SEP-6, SEP-12, and SEP-38 endpoints:

curl -X GET "https://<your-anchor-domain>/sep6/info" \
  -H "Authorization: Bearer eyJhbGciOiJFZERTQSI..."

Error responses

StatusErrorMeaning
400invalid_requestMissing or malformed account parameter
400invalid_transactionChallenge XDR is malformed or expired
401unauthorizedSignature does not match the account in the challenge
404not_foundAccount not recognized or challenge expired

Security notes

  • Never expose your Stellar secret key in client-side code or logs
  • The challenge transaction is single-use and time-limited (typically 5 minutes)
  • The anchor verifies the signature matches the public key from the challenge
  • JWTs are scoped to a single Stellar account
  • Store tokens securely; treat them like session credentials

On this page