bknd-session-handling
Use when managing user sessions in a Bknd application. Covers JWT token lifecycle, session persistence, automatic renewal, checking auth state, invalidating sessions, and handling expiration.
What this skill does
# Session Handling
Manage user sessions in Bknd: token persistence, session checking, auto-renewal, and invalidation.
## Prerequisites
- Bknd project with auth enabled (`bknd-setup-auth`)
- Auth strategy configured and working (`bknd-login-flow`)
- For SDK: `bknd` package installed
- For React: `@bknd/react` package installed
## When to Use UI Mode
- Viewing JWT configuration in admin panel
- Checking cookie settings
- Testing session expiration
**UI steps:** Admin Panel > Auth > Configuration > JWT/Cookie settings
## When to Use Code Mode
- Implementing session persistence in frontend
- Checking authentication state on page load
- Handling token expiration gracefully
- Implementing auto-refresh patterns
- Server-side session validation
## How Sessions Work in Bknd
Bknd uses **stateless JWT-based sessions**:
1. **Login** - Server creates signed JWT with user data, returns token
2. **Storage** - Token stored in cookie (automatic) or localStorage/header (manual)
3. **Requests** - Token sent with each request for authentication
4. **Validation** - Server validates signature and expiration
5. **Renewal** - Cookie can auto-renew; header tokens require manual refresh
**Key Concept:** No server-side session storage. Token itself is the session.
## Session Configuration
### JWT Settings
```typescript
import { defineConfig } from "bknd";
export default defineConfig({
auth: {
enabled: true,
jwt: {
secret: process.env.JWT_SECRET!, // Required for production
alg: "HS256", // Algorithm: HS256 | HS384 | HS512
expires: 604800, // 7 days in seconds
issuer: "my-app", // Token issuer claim
fields: ["id", "email", "role"], // User fields in token payload
},
},
});
```
**JWT options:**
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `secret` | string | `""` | Signing secret (256-bit min for production) |
| `alg` | string | `"HS256"` | HMAC algorithm |
| `expires` | number | - | Token lifetime in seconds |
| `issuer` | string | - | Issuer claim (iss) |
| `fields` | string[] | `["id","email","role"]` | User fields encoded in token |
### Cookie Settings
```typescript
{
auth: {
cookie: {
secure: process.env.NODE_ENV === "production", // HTTPS only
httpOnly: true, // No JS access
sameSite: "lax", // CSRF protection
expires: 604800, // Match JWT expiry
renew: true, // Auto-extend on activity
path: "/", // Cookie scope
pathSuccess: "/dashboard", // Redirect after login
pathLoggedOut: "/login", // Redirect after logout
},
},
}
```
**Cookie options:**
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `secure` | boolean | `true` | Require HTTPS |
| `httpOnly` | boolean | `true` | Block JavaScript access |
| `sameSite` | string | `"lax"` | `"strict"` \| `"lax"` \| `"none"` |
| `expires` | number | `604800` | Cookie lifetime (seconds) |
| `renew` | boolean | `true` | Auto-renew on requests |
| `pathSuccess` | string | `"/"` | Post-login redirect |
| `pathLoggedOut` | string | `"/"` | Post-logout redirect |
## SDK Approach
### Session Persistence with Storage
```typescript
import { Api } from "bknd";
// Persistent sessions (survives page refresh/browser restart)
const api = new Api({
host: "http://localhost:7654",
storage: localStorage, // Token persisted
});
// Session-only (cleared when tab closes)
const api = new Api({
host: "http://localhost:7654",
storage: sessionStorage, // Token cleared on tab close
});
// No persistence (token in memory only)
const api = new Api({
host: "http://localhost:7654",
// No storage = token lost on page refresh
});
```
### Check Session on App Start
```typescript
async function initializeAuth() {
const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
});
// Check if existing token is still valid
const { ok, data } = await api.auth.me();
if (ok && data?.user) {
console.log("Session valid:", data.user.email);
return { api, user: data.user };
}
console.log("No valid session");
return { api, user: null };
}
// On app mount
const { api, user } = await initializeAuth();
```
### Session State Management
```typescript
import { Api } from "bknd";
class SessionManager {
private api: Api;
private user: User | null = null;
private listeners: Set<(user: User | null) => void> = new Set();
constructor(host: string) {
this.api = new Api({ host, storage: localStorage });
}
// Initialize - call on app start
async init() {
const { ok, data } = await this.api.auth.me();
this.user = ok ? data?.user ?? null : null;
this.notifyListeners();
return this.user;
}
// Get current session
getUser() {
return this.user;
}
isAuthenticated() {
return this.user !== null;
}
// Login - creates new session
async login(email: string, password: string) {
const { ok, data, error } = await this.api.auth.login("password", {
email,
password,
});
if (!ok) throw new Error(error?.message || "Login failed");
this.user = data!.user;
this.notifyListeners();
return this.user;
}
// Logout - destroys session
async logout() {
await this.api.auth.logout();
this.user = null;
this.notifyListeners();
}
// Refresh session (re-validate token)
async refresh() {
const { ok, data } = await this.api.auth.me();
this.user = ok ? data?.user ?? null : null;
this.notifyListeners();
return this.user;
}
// Subscribe to session changes
subscribe(callback: (user: User | null) => void) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
private notifyListeners() {
this.listeners.forEach((cb) => cb(this.user));
}
}
type User = { id: number; email: string; role?: string };
// Usage
const session = new SessionManager("http://localhost:7654");
await session.init();
session.subscribe((user) => {
console.log("Session changed:", user?.email || "logged out");
});
```
### Cookie-Based Sessions (Automatic)
```typescript
const api = new Api({
host: "http://localhost:7654",
tokenTransport: "cookie", // Use httpOnly cookies
});
// Login sets cookie automatically
await api.auth.login("password", { email, password });
// All requests include cookie automatically
await api.data.readMany("posts");
// Logout clears cookie
await api.auth.logout();
```
**Cookie mode advantages:**
- HttpOnly = XSS protection (JavaScript can't access token)
- Auto-renewal on every request (if `cookie.renew: true`)
- No manual token management
- Automatic CSRF protection with `sameSite`
### Header-Based Sessions (Manual)
```typescript
const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
tokenTransport: "header", // Default
});
// Token stored in localStorage, sent via Authorization header
await api.auth.login("password", { email, password });
// Token automatically included:
// Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```
## Handling Session Expiration
### Detect Expired Token
```typescript
async function makeAuthenticatedRequest<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
// Check if error is due to expired session
if (isAuthError(error)) {
// Session expired - redirect to login or refresh
await handleExpiredSession();
}
throw error;
}
}
function isAuthError(error: unknown): boolean {
if (error instanceof Error) {
return error.message.includes("401") || error.message.includes("Unauthorized");
}
return false;
}
async function handleExpiredSession() {
// Option 1: RRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.