Skip to content
Prividium

Manage Auth Tokens Programmatically

This guide explains how to programmatically manage authentication tokens in Prividium using Sign-In With Ethereum (SIWE). Tokens are required for authenticated access to the Prividium API and RPC endpoints.

Token Lifecycle Overview

  1. Issue: Request a SIWE message, sign it with your wallet, and exchange it for a session token
  2. Check: Query the current session endpoint to verify token validity and expiration
  3. Refresh: When expired, repeat the SIWE flow to get a new token

Prerequisites

You need a private key and its derived address. You can use cast to generate a new keypair:

$ cast wallet new
Successfully created new keypair.
Address:     0x9317a78Dc814297Defe678918A84084C427a1e59
Private key: 0xd9ad2e2da24635777b184f591ba8cf6aaf428879034030022f088a68f2390b05

The private key is used to sign messages. Before authenticating, the address must be associated with a user account.

To link an address to a user, log in to the Admin Panel and add the address to the user account that will be used for programmatic access.

Step 1: Request a SIWE Message

To get a token, first request a SIWE message from the API.

Endpoint

POST /api/siwe-messages

Request Body

FieldTypeRequiredDescription
addressstringYesThe wallet address (hex format, e.g., 0x...)
domainstringYesYour user panel domain (i.e. user-panel.prividium.com)

Example Request

curl -X POST http://localhost:8000/api/siwe-messages \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0xYourWalletAddress",
    "domain": "localhost:3000"
  }'

Example Response

{
  "nonce": "abc123xyz",
  "msg": "localhost:3000 wants you to sign in with your Ethereum account:\n0xYourWalletAddress\n\nLogin to Prividium™ chain\n\nURI: prividium:access\nVersion: 1\nChain ID: 260\nNonce: abc123xyz\nIssued At: 2024-01-15T10:00:00.000Z\nExpiration Time: 2024-01-15T10:05:00.000Z",
  "nonceToken": "eyJhbGciOiJIUzI1NiJ9..."
}

Capture both msg and nonceToken — you need both in Step 2.

Step 2: Sign and Exchange for Token

Sign the message with your wallet and send it to the login endpoint.

Endpoint

POST /api/auth/login/crypto-native

Request Body

FieldTypeRequiredDescription
messagestringYesThe SIWE message from Step 1
signaturestringYesThe wallet signature (hex format)
nonceTokenstringYesThe nonce token from Step 1 response

Example Request

curl -X POST http://localhost:8000/api/auth/login/crypto-native \
  -H "Content-Type: application/json" \
  -d '{
    "message": "<siwe_message_from_step_1>",
    "signature": "0xYourSignature...",
    "nonceToken": "<nonce_token_from_step_1>"
  }'

Example Response

{
  "token": "your-session-token-here",
  "expiresAt": "2024-01-15T18:00:00.000Z"
}

The token is your session token for authenticated API calls. The expiresAt field indicates when the token expires.

Step 3: Refresh Expired Tokens

When a token expires, repeat the SIWE flow (Steps 1-2) to get a new token. There is no dedicated refresh endpoint; the full authentication flow must be repeated.

Best Practice

Check the token expiration before each API interaction. The expiration time can be safely cached in memory, and can be verified again at any time using the GET /api/auth/current-session endpoint.

  1. Save expiration time locally near the token value.
  2. If the token is expired or about to expire, initiate a new SIWE flow.
  3. Update your stored token with the new one.

Check Token Expiration

Verify token validity and expiration at any time using the current-session endpoint:

Endpoint

GET /api/auth/current-session

Headers

HeaderValue
AuthorizationBearer <your-session-token>

Example Request

curl -X GET http://localhost:8000/api/auth/current-session \
  -H "Authorization: Bearer <your-session-token>"

Example Response

{
  "type": "user",
  "expiresAt": "2024-01-15T18:00:00.000Z"
}
FieldTypeDescription
typestringSession type: user (or legacy tenant / service when those features are enabled)
expiresAtstringISO 8601 timestamp when the token expires

Response Status Codes

Prividium follows standard HTTP status code conventions for the REST API:

StatusDescription
200-299Success. No action required.
401Authentication invalid. Invalidate the token and re-authenticate.
403User lacks access to the requested resource. Token remains valid.

The JSON-RPC standard does not use HTTP 401 and 403 for authentication errors. Prividium uses custom RPC error codes instead:

RPC Error CodeDescription
-32090Authentication failed. Re-authenticate to obtain a new token.
-32001User lacks access to the requested resource. Token is valid.

Example: Complete Authentication Script

Using REST programmatically

rest-authentication.ts
import type { Hex } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const PRIVIDIUM_API_BASE_URL = process.env.PRIVIDIUM_API_BASE_URL ?? 'http://localhost:8000';
const DOMAIN = process.env.DOMAIN ?? 'localhost:3000';
const PRIVATE_KEY = process.env.PRIVATE_KEY;

let currentToken: string;
let tokenExpiresAt = new Date(0);

async function apiCall(path: string, init = {}) {
    const url = new URL(path, PRIVIDIUM_API_BASE_URL);
    const response = await fetch(url, init);

    if (!response.ok) {
        const detail = await response.text();
        throw new Error(`Request failed (${response.status}) ${path}: ${detail}`);
    }

    return response.json();
}

async function authenticate() {
    if (!PRIVATE_KEY) throw new Error('Missing PRIVATE_KEY in environment');
    const account = privateKeyToAccount(PRIVATE_KEY as Hex);

    // 1. Request a SIWE message
    const siweData = await apiCall('/api/siwe-messages', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            address: account.address,
            domain: DOMAIN
        })
    });

    // 2. Sign the returned message
    const signature = await account.signMessage({
        message: siweData.msg
    });

    // 3. Exchange for a session token
    const loginData = await apiCall('/api/auth/login/crypto-native', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            message: siweData.msg,
            signature,
            nonceToken: siweData.nonceToken
        })
    });

    // 4. Use the token in Authorization: Bearer <token> headers
    currentToken = loginData.token;
    tokenExpiresAt = new Date(loginData.expiresAt);
    console.log('Token expires at:', tokenExpiresAt.toISOString());

    return currentToken;
}

async function ensureAuthenticated() {
    if (!currentToken || new Date() >= tokenExpiresAt) {
        console.log('Session missing or expired. Authenticating...');
        await authenticate();
    }
}

async function fetchUserProfile() {
    await ensureAuthenticated();

    const user = await apiCall('/api/profiles/me', {
        method: 'GET',
        headers: {
            Authorization: `Bearer ${currentToken}`
        }
    });

    console.log('Authenticated user:', user.displayName ?? user.id);
    return user;
}

async function callRpc(method: string, params = []) {
    await ensureAuthenticated();

    const rpcResponse = await apiCall('/rpc', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${currentToken}`
        },
        body: JSON.stringify({
            jsonrpc: '2.0',
            id: 1,
            method,
            params
        })
    });

    if (rpcResponse.error) throw new Error(`RPC ${method} failed: ${JSON.stringify(rpcResponse.error)}`);

    return rpcResponse.result;
}

export async function main() {
    const token = await authenticate();
    console.log('Authorization header:', `Bearer ${token}`);

    await fetchUserProfile();

    const chainId = await callRpc('eth_chainId');
    console.log('Authenticated RPC chain ID:', Number(chainId));
}

main().catch(console.error);

Using Prividium™ SDK

The following Node.js example uses the /prividium/siwe entry point to authenticate, inspect the issued token, call the profile API, and make an authenticated RPC request.

complete-authentication.ts
import { createPrividiumClient } from 'prividium';
import { createPrividiumSiweChain, type PrividiumSiweConfig } from 'prividium/siwe';
import { defineChain, type Hex } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';


const PRIVATE_KEY = process.env.PRIVATE_KEY;
const PRIVIDIUM_API_BASE_URL = process.env.PRIVIDIUM_API_BASE_URL ?? 'http://localhost:8000';
const DOMAIN = process.env.DOMAIN ?? 'localhost:3000';
const CHAIN_ID = Number(process.env.CHAIN_ID ?? '6565');

export async function main() {
    if (!PRIVATE_KEY) throw new Error('MISSING PRIVATE KEY');

    const account = privateKeyToAccount(PRIVATE_KEY as Hex) as unknown as PrividiumSiweConfig['account'];
    const chain = defineChain({
        id: CHAIN_ID,
        name: 'Prividium Local',
        nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
        rpcUrls: { default: { http: [] } }
    }) as unknown as PrividiumSiweConfig['chain'];

    const prividium = createPrividiumSiweChain({
        account,
        chain,
        prividiumApiBaseUrl: PRIVIDIUM_API_BASE_URL,
        domain: DOMAIN,
        onReauthenticate: () => {
            console.info('Prividium session refreshed');
        }
    });

    const client = createPrividiumClient({
        chain: prividium.chain,
        transport: prividium.transport,
        account: prividium.address
    });

    const session = await prividium.authorize();
    console.log('Token expires at:', session.expiresAt.toISOString());

    const user = await prividium.fetchUser();
    console.log('Authenticated user:', user.displayName ?? user.id);

    const headers = prividium.getAuthHeaders();
    if (!headers) {
        throw new Error('Expected auth headers after authorize()');
    }
    console.log('Authorization header:', headers.Authorization);

    const chainId = await client.getChainId();
    console.log('Authenticated RPC chain ID:', chainId);

    return { prividium, client };
}

main().catch(console.error);

Security Considerations

  • Nonce expiration: SIWE messages expire after a configured time (default: 5 minutes)
  • Single use: Each SIWE nonce can only be used once
  • Rate limiting: Maximum 10 challenge requests per 5 minutes per address