@saganta/stellar-appkit-siws-verify
v1.9.46
Published
Server-side SIWS signature/envelope verification.
Readme
@saganta/stellar-appkit-siws-verify
Server-side SIWS (Sign-In With Stellar) signature verification.
📖 Official Docs · 🎮 Official Demos
| | Link | |---|---| | 📖 Official Docs | stellar-appkit.saganta.com | | 🎮 Official Demos | demos.stellar-appkit.saganta.com | | 💻 GitHub | github.com/sagantaHQ/stellar-appkit |
Install
npm install @saganta/stellar-appkit-siws-verifyUsage
import { verifySiws } from '@saganta/stellar-appkit-siws-verify';
// The client (browser) sends these after signing:
// { message, signedMessage, signerAddress, signedData }
const result = await verifySiws(
{ message, signedMessage, signerAddress, signedData },
{
expectedDomain: 'app.example.com', // must match the domain in the SIWS message
expectedNonce: nonce, // the nonce you issued earlier
}
);
if (result.ok) {
// Verification succeeded — the user proved they own signerAddress
console.log(result.claims.address);
console.log(result.claims.expirationTime);
// Set a session cookie, issue a JWT, etc.
} else {
// Verification failed
console.log(result.reason);
// Enable debug mode to see exactly what was tried:
// const result = await verifySiws(payload, { ...opts, debug: true });
// console.log(result.diagnostics);
}How it works
Different Stellar wallets sign messages differently:
- Freighter signs
sha256("Stellar Signed Message:\n" + message)(SEP-0053) - Albedo signs a server-derived hash
- xBull signs raw UTF-8 bytes
- Ledger signs raw UTF-8 bytes (direct signer)
The signedData field (base64 of the exact bytes the wallet signed) is surfaced by every connector in @saganta/stellar-appkit. The verifier tries 8+ candidate byte sequences so you don't need per-wallet verification logic:
signedData(if present)utf8(message)— raw bytessha256("Stellar Signed Message:\n" + message)— SEP-0053 (Freighter)sha256(message)— generic prehashsha512(message)— SHA-512 prehashsha512(message)truncated to 32 bytessha256("\x00" + message)— null-byte domain prefixutf8(message with CRLF)— Windows line endings
Debug mode
When verification fails, enable debug: true to see exactly what was tried:
const result = await verifySiws(payload, {
expectedDomain: 'localhost',
expectedNonce: nonce,
debug: true,
});
if (!result.ok) {
console.log(result.diagnostics);
// {
// signatureByteLength: 64,
// candidatesTried: [
// { label: 'signedData', byteLength: 32, verified: false },
// { label: 'utf8(message)', byteLength: 156, verified: false },
// { label: 'sha256(SEP-0053)', byteLength: 32, verified: true },
// ...
// ]
// }
}API
function verifySiws(
payload: {
message: string; // the SIWS message text
signedMessage: string; // base64 signature
signerAddress: string; // G... address
signedData?: string; // base64 of the exact bytes signed (optional but recommended)
},
opts: {
expectedDomain: string; // must match the domain in the SIWS message
expectedNonce: string; // the nonce you issued earlier
debug?: boolean; // default: false
}
): Promise<{
ok: boolean;
claims?: {
address: string;
domain: string;
nonce: string;
issuedAt: string;
expirationTime: string;
};
reason?: string;
diagnostics?: { ... };
}>Documentation
- SIWS guide — full client + server flow, automatic SIWS, session lifecycle
- Live demos — SIWS sign-in, session middleware, debug verification
- Full docs — complete documentation
License
MIT © Saganta
