newOS for Developers
Perpetual Session
Never-expire auth with automatic token refresh
advanced1 endpointauth
When to Use
- • Keeping users logged in indefinitely
- • Progressive web apps that need offline-first auth
- • Background services that make authenticated requests
- • Avoiding session expiry during long user sessions
Token Lifecycle
7 days
JWT validity period
6 days
Recommended refresh point
1 hour
Check interval
Prerequisites
- • Existing valid Newgraph JWT (from initial
signIn()) - • Token stored in
localStorageundernewsafe-auth-token
API Endpoint
| Endpoint | Method | Auth | Description |
|---|---|---|---|
| /auth/generateSessionToken | POST | Existing JWT | Generate a fresh JWT |
Implementation Pattern
Token Refresh Service
// perpetualSession.ts
import { newgraphClient, stage } from "newgraph-signals";
import { ContentType } from "@newstackdev/iosdk-newgraph-client-js";
const JWT_LOCALSTORAGE = "newsafe-auth-token";
const REFRESH_INTERVAL = 60 * 60 * 1000; // 1 hour
const REFRESH_THRESHOLD = 6 * 24 * 60 * 60 * 1000; // 6 days
// Decode JWT to get expiry (without verification)
function getTokenExpiry(jwt: string): number | null {
try {
const payload = JSON.parse(atob(jwt.split(".")[1]));
return payload.exp * 1000; // Convert to ms
} catch {
return null;
}
}
// Check if token needs refresh
function shouldRefresh(jwt: string): boolean {
const expiry = getTokenExpiry(jwt);
if (!expiry) return false;
const timeUntilExpiry = expiry - Date.now();
return timeUntilExpiry < REFRESH_THRESHOLD;
}
// Refresh the token
async function refreshToken(): Promise<string | null> {
const storedJwt = localStorage.getItem(JWT_LOCALSTORAGE);
if (!storedJwt) return null;
try {
const sessionRes = await newgraphClient.api.request({
baseUrl: `https://api${stage === "eu-prod" ? "" : `-${stage.split(/-/)[1]}`}.newgra.ph/v1/auth`,
path: "/generateSessionToken",
method: "POST",
body: {
referer: "newgra.ph",
appOwner: "newcoinos",
redirectUrl: window.location.origin + "/start",
scopes: [],
},
secure: true,
type: ContentType.Json,
format: "json",
});
const newJwt = sessionRes.data.jwt;
// Store and apply new token
localStorage.setItem(JWT_LOCALSTORAGE, newJwt);
newgraphClient.updateToken(`newsafe ${newJwt}`);
console.log("[perpetual-session] Token refreshed");
return newJwt;
} catch (error) {
console.error("[perpetual-session] Refresh failed:", error);
return null;
}
}Start the Refresh Loop
// Initialize perpetual session on app load
export function initPerpetualSession() {
const storedJwt = localStorage.getItem(JWT_LOCALSTORAGE);
if (!storedJwt) return;
// Apply stored token
newgraphClient.updateToken(`newsafe ${storedJwt}`);
// Check immediately if refresh needed
if (shouldRefresh(storedJwt)) {
refreshToken();
}
// Set up periodic check
setInterval(() => {
const jwt = localStorage.getItem(JWT_LOCALSTORAGE);
if (jwt && shouldRefresh(jwt)) {
refreshToken();
}
}, REFRESH_INTERVAL);
console.log("[perpetual-session] Initialized");
}
// Call on app startup
initPerpetualSession();Best Practices
✓ Do
- • Refresh before expiry (6-day threshold)
- • Use background refresh intervals
- • Handle 401 responses gracefully
- • Log refresh events for debugging
✗ Don't
- • Wait until token expires
- • Refresh on every API call
- • Store tokens in sessionStorage
- • Ignore refresh failures
Edge Cases
Token Already Expired
If the token has already expired, /generateSessionToken will return 401. Redirect the user to re-authenticate via phone/OAuth.
Multiple Tabs
Each tab runs its own refresh loop. Use localStorage events to sync tokens across tabs.
Network Offline
If refresh fails due to network, retry on next interval. The 6-day threshold provides buffer for temporary outages.
🧪 Quick Test
Sign in to Test
Use the sidebar to sign in with your phone number