Authentication
Nodius uses your Solana wallet for authentication. Wallet-native signup. API keys and sessions available for low-latency automated traffic. Your Ed25519 keypair is your identity.
Three authentication modes are available:
| Mode | Best for | How it works |
|---|---|---|
| Per-request signature (v2) | Bots, scripts, automated systems | Sign every request with your keypair |
| Session token | WebSocket, dApps, interactive use | Authenticate once, get a bearer token |
| API key | Simple integrations, scripts | Generate a persistent API key |
Mode 1: Per-Request Wallet Signature (v2)
Every request is independently signed with your Solana keypair. This is the canonical authentication mode and the most secure option for automated systems โ there's no token to steal or expire.
Note: Auth v1 is removed. Only v2 is supported. The server verifies using the actual HTTP method and path from the request (server-side), not client-supplied headers. There is no
X-Auth-Version,X-Http-Method, orX-Http-Pathheader.
How It Works
- Compute the SHA-256 hex digest of the request body
- Construct the canonical signing message (see below)
- Sign the message with your Ed25519 private key
- Include the signature and metadata in request headers
Canonical Signing Message
solana-nodius:v2:{METHOD}:{PATH}:{TIMESTAMP}:{NONCE}:{BODY_HASH}
| Component | Description |
|---|---|
METHOD |
HTTP method, e.g. POST (server uses actual request method) |
PATH |
Request path, e.g. / or /rpc (server uses actual request path) |
TIMESTAMP |
Unix timestamp in seconds (must be within ยฑ60s of server time) |
NONCE |
Random string, 1-128 chars, alphanumeric plus -_:., (prevents replay attacks) |
BODY_HASH |
SHA-256 hash of the request body (hex-encoded, lowercase) |
Required Headers
| Header | Value |
|---|---|
X-Pubkey |
Your Solana public key (base58, 32 bytes) |
X-Signature |
Ed25519 signature of the signing message (base58, 64 bytes) |
X-Timestamp |
Unix timestamp used in the signing message |
X-Nonce |
Nonce used in the signing message (1-128 chars) |
No X-Auth-Version, X-Http-Method, or X-Http-Path headers are needed โ the server infers the method and path from the actual request.
Replay Protection
Each (pubkey, nonce) pair can only be used once. The server maintains a nonce cache using Redis SETNX with a TTL. Reusing a nonce results in a -32000 error (replay detected).
The timestamp must be within ยฑ60 seconds of the server's clock. Requests outside this window are rejected.
Code Example (TypeScript)
This low-level example uses @solana/web3.js, tweetnacl, and bs58 directly. The SDK handles this signing path for you when you pass any compatible KeypairLike.
import { Keypair } from "@solana/web3.js";
import { sign } from "tweetnacl";
import { createHash, randomBytes } from "crypto";
import bs58 from "bs58";
function signRequest(keypair: Keypair, method: string, path: string, body: string) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = randomBytes(16).toString("hex");
const bodyHash = createHash("sha256").update(body).digest("hex");
// Canonical v2 signing message
const message = `solana-nodius:v2:${method}:${path}:${timestamp}:${nonce}:${bodyHash}`;
const messageBytes = new TextEncoder().encode(message);
const signature = sign.detached(messageBytes, keypair.secretKey);
return {
"X-Pubkey": keypair.publicKey.toBase58(),
"X-Signature": bs58.encode(Buffer.from(signature)),
"X-Timestamp": timestamp,
"X-Nonce": nonce,
};
}
// Usage
const keypair = Keypair.generate();
const body = JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "getSlot",
});
const headers = signRequest(keypair, "POST", "/", body);
const response = await fetch("https://rpc.nodius.xyz/", {
method: "POST",
headers: {
"Content-Type": "application/json",
...headers,
},
body,
});
console.log("Credits remaining:", response.headers.get("X-Credits-Remaining"));
const result = await response.json();
console.log("Slot:", result.result);
Code Example (Python)
import time
import os
import hashlib
import json
import requests
import base58
from solders.keypair import Keypair
from nacl.signing import SigningKey
keypair = Keypair()
body = json.dumps({
"jsonrpc": "2.0",
"id": 1,
"method": "getSlot"
})
timestamp = str(int(time.time()))
nonce = os.urandom(16).hex()
body_hash = hashlib.sha256(body.encode()).hexdigest()
# Canonical v2 signing message
message = f"solana-nodius:v2:POST:/:{ timestamp}:{nonce}:{body_hash}"
signing_key = SigningKey(bytes(keypair)[:32])
signature = signing_key.sign(message.encode()).signature
response = requests.post(
"https://rpc.nodius.xyz/",
headers={
"Content-Type": "application/json",
"X-Pubkey": str(keypair.pubkey()),
"X-Signature": base58.b58encode(signature).decode("ascii"),
"X-Timestamp": timestamp,
"X-Nonce": nonce,
},
data=body,
)
print("Credits remaining:", response.headers.get("X-Credits-Remaining"))
print("Result:", response.json())
Mode 2: Session Tokens
Session tokens are useful when per-request signing is impractical โ WebSocket connections, browser dApps, or situations where you want to authenticate once and reuse a bearer token.
How It Works
- Request a challenge from the server
- Sign the challenge with your keypair
- Submit the signed challenge to receive a session token
- Use the token as a
Bearertoken in subsequent requests
Step 1: Request a Challenge
POST /auth/challenge
Content-Type: application/json
{
"pubkey": "YourPublicKeyBase58..."
}
Response:
{
"challenge": "a1b2c3d4e5f67890abcdef1234567890a1b2c3d4e5f67890abcdef1234567890",
"expires_in": 300
}
The challenge is a 64-character hex string (32 random bytes, hex-encoded). It is valid for 300 seconds (5 minutes).
Step 2: Sign and Verify
Sign the raw challenge hex string with your Ed25519 keypair, then submit:
POST /auth/verify
Content-Type: application/json
{
"pubkey": "YourPublicKeyBase58...",
"challenge": "a1b2c3d4e5f67890abcdef1234567890a1b2c3d4e5f67890abcdef1234567890",
"signature": "Base58EncodedSignature..."
}
Response:
{
"token": "64-char-hex-session-token...",
"expires_in": 3600
}
The token is a 64-character hex string (256-bit random, hex-encoded). It expires after 3600 seconds (1 hour) with a sliding TTL that refreshes on each use.
Step 3: Use the Token
Include the token in the Authorization header:
POST /
Authorization: Bearer <token>
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"getSlot"}
This also works at POST /rpc.
For WebSocket connections, use the Authorization header or Sec-WebSocket-Protocol header:
Authorization: Bearer <token>
Or via the Sec-WebSocket-Protocol header (for clients that don't support custom headers):
Sec-WebSocket-Protocol: auth.<token>, solana-rpc
Both auth.<token> and solana-rpc subprotocols must be sent together. The server responds with Sec-WebSocket-Protocol: solana-rpc (the token is never echoed back).
Revoking Sessions
To revoke the current session:
POST /auth/logout
Authorization: Bearer <token>
Session Details
- Duration: 1 hour from issuance (sliding TTL refreshed on each use)
- Absolute max: 24 hours โ sessions are forcibly expired regardless of activity
- Renewal: Request a new challenge/token before expiry. There is no refresh mechanism.
- Revocation: Use
POST /auth/logoutto revoke the current session token. - Concurrency: Up to 10 active sessions per account.
Code Example
import { Keypair } from "@solana/web3.js";
import { sign } from "tweetnacl";
import bs58 from "bs58";
async function getSessionToken(
endpoint: string,
keypair: Keypair
): Promise<string> {
// Step 1: Get challenge
const challengeRes = await fetch(`${endpoint}/auth/challenge`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
pubkey: keypair.publicKey.toBase58(),
}),
});
const { challenge } = await challengeRes.json();
// Step 2: Sign the raw challenge hex string and verify
const challengeBytes = new TextEncoder().encode(challenge);
const signature = sign.detached(challengeBytes, keypair.secretKey);
const verifyRes = await fetch(`${endpoint}/auth/verify`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
pubkey: keypair.publicKey.toBase58(),
challenge,
signature: bs58.encode(Buffer.from(signature)),
}),
});
const { token } = await verifyRes.json();
return token;
}
// Usage
const keypair = Keypair.generate();
const token = await getSessionToken("https://rpc.nodius.xyz", keypair);
// Now use the token for requests
const res = await fetch("https://rpc.nodius.xyz/", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getSlot" }),
});
Mode 3: API Key
For simple integrations, you can generate a persistent API key. This avoids per-request signing while providing a long-lived credential.
Generate an API Key
POST /account/api-key
Requires per-request wallet signature auth (or an existing session token).
Response:
{
"ok": true,
"api_key": "srpc_live_<base64url>...",
"message": "Save this key โ it will not be shown again."
}
Use the API Key
Send it via the X-Api-Key header:
POST /
X-Api-Key: <api-key>
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"getSlot"}
API keys do not expire but can be regenerated (which invalidates the previous key).
Two API key formats are accepted:
- New format: srpc_live_<base64url> (recommended)
- Legacy format: 64-character hex string
API Key for WebSocket
API keys also work for WebSocket connections via Sec-WebSocket-Protocol:
Sec-WebSocket-Protocol: auth.<api-key>, solana-rpc
Or via the X-Api-Key header (for clients that support custom WebSocket headers).
Auth Priority
When multiple auth headers are present, the server checks in this order:
- X-Api-Key โ if present, API key auth is used (fastest)
- X-Pubkey โ if present, per-request wallet signature auth is used
- Authorization: Bearer โ if present, session token auth is used
If none are present, the request is rejected with a -32000 error.
Which Mode Should I Use?
Use per-request signatures if: - You're running a bot or automated system - You have access to the keypair at request time - You want maximum security (no token to leak) - You're making HTTP requests (not WebSocket)
Use session tokens if: - You need WebSocket connections - You're building a dApp or UI - You want to authenticate once and make many requests - You're using a language/environment where per-request signing is cumbersome
Use API keys if: - You want the simplest possible integration - You're testing or prototyping - You're comfortable storing a long-lived secret - You need both HTTP and WebSocket with one credential
SDK Authentication
The @nodius/sdk package handles all authentication automatically:
const rpc = new NodiusClient("https://rpc.nodius.xyz", {
keypair,
// Default: per-request signing (v2)
// auth: "signature",
// Or use session tokens (auto-renews before expiry)
// auth: "session",
});
// Just make calls โ auth is handled
const slot = await rpc.call("getSlot");