Skip to main content

Sessions & Authentication

Ductape sessions are JWT-based tokens that identify a user across product operations. Starting, verifying, refreshing, and revoking sessions is backend-only — use @ductape/sdk with an access key on your API or BFF.

The browser uses a publishable key and never calls session lifecycle APIs. After login, your backend returns a session token; the frontend passes it as session on every Ductape request.

How it works

  1. User signs in through your auth (email/password, OAuth, etc.).
  2. Your backend validates credentials and calls ductape.sessions.start() with @ductape/sdk.
  3. Your API returns token (format tag:jwt) and optionally refreshToken to the client.
  4. The frontend stores the token and includes session: token on databases, storage, actions, and other calls.
  5. Refresh and revoke stay on the backend — expose your own /auth/refresh and /auth/logout routes that call the SDK.

Starting a session (backend only)

Backend only — @ductape/sdk

Session lifecycle methods (sessions.start, sessions.verify, sessions.refresh, sessions.revoke, etc.) require an access key and must run on your server. They are not available from @ductape/client, @ductape/react, or @ductape/vue when using a publishable key.

import Ductape from '@ductape/sdk';

const ductape = new Ductape({
accessKey: process.env.DUCTAPE_ACCESS_KEY!,
product: 'my-product',
env: 'prd',
});

// After your own auth validates the user
const result = await ductape.sessions.start({
tag: 'user-session',
data: {
userId: 'user_123',
details: {
email: 'user@example.com',
role: 'user',
},
},
});

// Return to the browser via your login API
res.json({
sessionToken: result.token,
refreshToken: result.refreshToken,
expiresAt: result.expiresAt,
});

Custom duration and session types

// Longer-lived admin session
const adminSession = await ductape.sessions.start({
tag: 'admin-session',
data: {
userId: 'admin_456',
details: { email: 'admin@company.com', role: 'admin' },
},
expiresIn: 86400, // 24 hours
});

// Short-lived checkout flow
const checkoutSession = await ductape.sessions.start({
tag: 'checkout-session',
data: {
userId: 'user_123',
details: { cartId: 'cart_789' },
},
expiresIn: 1800, // 30 minutes
});

Verifying and refreshing (backend only)

Verify a token when your API needs to read encrypted session data or check expiry:

const verified = await ductape.sessions.verify({
tag: 'user-session',
token: sessionTokenFromClient,
});

console.log(verified.data.userId);
console.log(verified.expiresAt);

Refresh before expiry — call from a backend route, not from the browser SDK:

const renewed = await ductape.sessions.refresh({
tag: 'user-session',
refreshToken: refreshTokenFromClient,
});

// Return new tokens to the client
res.json({ sessionToken: renewed.token, refreshToken: renewed.refreshToken });

Revoking sessions (backend only)

// Logout — revoke the current session
const verified = await ductape.sessions.verify({
tag: 'user-session',
token: sessionToken,
});

await ductape.sessions.revoke({
tag: 'user-session',
sessionId: verified.sessionId,
});

// Log out all devices for a user
await ductape.sessions.revokeAll({
tag: 'user-session',
identifier: 'user_123',
});

Using session tokens on the frontend

Once your backend returns a session token, pass it on every Ductape call. With a publishable key, requests without session are rejected.

Store the token after login

Your login flow calls your backend; the backend starts the Ductape session and returns the token:

// Your app's login handler (browser)
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});

const { sessionToken, refreshToken } = await res.json();
localStorage.setItem('ductape_session', sessionToken);
localStorage.setItem('ductape_refresh', refreshToken);

Pass session on every request

import { Ductape } from '@ductape/client';

const ductape = new Ductape({
publishableKey: 'pk_…',
product: 'my-product',
env: 'prd',
});

function getSession(): string {
const token = localStorage.getItem('ductape_session');
if (!token) throw new Error('Not authenticated');
return token;
}

// Databases
const users = await ductape.databases.query({
table: 'users',
session: getSession(),
});

// Storage
await ductape.storage.upload({
bucket: 'avatars',
key: 'user-123.png',
file: blob,
session: getSession(),
});

// Actions
await ductape.actions.run({
action: 'send-welcome-email',
session: getSession(),
input: { userId: 'user_123' },
});

Refresh and logout via your backend

Do not call ductape.sessions.refresh() from the browser. Proxy through your API:

async function refreshSession() {
const refreshToken = localStorage.getItem('ductape_refresh');
const res = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});
const { sessionToken, refreshToken: newRefresh } = await res.json();
localStorage.setItem('ductape_session', sessionToken);
localStorage.setItem('ductape_refresh', newRefresh);
}

async function logout() {
await fetch('/api/auth/logout', {
method: 'POST',
headers: {
Authorization: `Bearer ${localStorage.getItem('ductape_session')}`,
},
});
localStorage.removeItem('ductape_session');
localStorage.removeItem('ductape_refresh');
}

Analytics

After login, attach the session to product analytics events:

ductape.analytics.identify(sessionToken);

See also