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
| Method | Path | Description |
|---|---|---|
| GET | /auth | Request a challenge transaction |
| POST | /auth | Submit 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
| Status | Error | Meaning |
|---|---|---|
| 400 | invalid_request | Missing or malformed account parameter |
| 400 | invalid_transaction | Challenge XDR is malformed or expired |
| 401 | unauthorized | Signature does not match the account in the challenge |
| 404 | not_found | Account 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