newOS for Developers
Authentication
Session management, login flows, and token handling for newOS.
packages/newgraph-signals/src/actions/auth.ts (222 lines)
Quick Reference
Core Functions
current— Get current authenticated usersignIn— Exchange Firebase token for Newgraph sessionsignOut— Clear session and cachecreate— Create new user account
OAuth & Utilities
signInWithX— Twitter/X OAuth flowclearLocalState— Reset local datagetOnesignalAuthHash— Get OneSignal hash
Reactive Signals
token— JWT Signal (newsafe {jwt})_current— Current user Signalwin— Safe window reference
Storage Keys
newsafe-auth-token— JWT in localStoragepre-signin-redirect— Redirect URL after authr— Referrer ID for invitations
Token Flow
The authentication flow exchanges a Firebase ID token for a Newgraph session JWT.
Firebase ID Token → POST /auth/generateSessionToken → Newgraph JWT
↓
localStorage["newsafe-auth-token"]
↓
newgraphClient.updateToken(`newsafe ${jwt}`)The JWT must be prefixed with newsafe (note the space) when setting the Authorization header. This is the single most common source of auth errors.
// CORRECT
newgraphClient.updateToken(`newsafe ${jwt}`);
// WRONG - missing prefix
newgraphClient.updateToken(jwt);API Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| /auth/generateSessionToken | POST | Exchange Firebase token for Newgraph JWT |
| /auth/onesignal | GET | Get OneSignal external ID auth hash |
| /auth/provider/twitter2 | GET | Initiate Twitter/X OAuth2 flow |
| /user | GET | Get current authenticated user |
| /user | POST | Create new user account |
current
ProgressiveHandlerFetch the current authenticated user's private profile. Uses the stored JWT to authenticate.
current(params?: { autostart: boolean })
: ProgressiveHandlerResponse<UserReadPrivateResponse>| Parameter | Type | Default | Description |
|---|---|---|---|
| autostart | boolean | false | Whether to auto-execute on call |
API Endpoint
api.user.currentList()Nuances
- Returns empty object if no token is present
- Result cached to IndexedDB via
cacheUsers() - Updates
_currentSignal reactively - Tracks execution count via
_currentShared.executions - Also exported as
currentPassivewithout request caching key
Usage
import { current, _current } from "newgraph-signals/actions/auth";
// Initialize and execute
const [user, progress] = current();
progress.value.exec();
await progress.value.promise;
// Access result via signal
console.log(_current.value); // UserReadPrivateResponse
// Or via returned signal
console.log(user.value);signIn
AsyncExchange a Firebase ID token for a Newgraph session JWT and establish the session.
signIn(
newgraphToken: string,
opts?: { invitorId?: string }
): Promise<string>| Parameter | Type | Required | Description |
|---|---|---|---|
| newgraphToken | string | Yes | Firebase ID token from phone/OAuth auth |
| opts.invitorId | string | No | Referrer user ID for invitation tracking |
API Request Body
POST /auth/generateSessionToken
{
referer: "newgra.ph",
appOwner: "newcoinos",
redirectUrl: "https://os.newcoin.org/start",
scopes: [],
r: invitorId // optional
}Behavior
- Sets Firebase token on client:
newgraphClient.updateToken(firebaseToken) - Calls
/auth/generateSessionTokento exchange for JWT - Stores JWT in localStorage:
newsafe-auth-token - Updates client with prefixed token:
newsafe {jwt} - Fetches current user and caches to IndexedDB
- Updates
_currentsignal with user data
Usage
import { signIn } from "newgraph-signals/actions/auth";
// After Firebase phone verification
const firebaseToken = await firebaseUser.getIdToken();
const jwt = await signIn(firebaseToken);
console.log("Session established:", jwt);create
AsyncCreate a new user account with a Firebase token.
create(
firebaseToken: string,
user: Partial<UserReadPrivateResponse>
): Promise<UserReadPrivateResponse>| Parameter | Type | Required | Description |
|---|---|---|---|
| firebaseToken | string | Yes | Firebase ID token after phone verification |
| user.username | string | No | Unique username |
| user.displayName | string | No | Display name shown in UI |
API Endpoint
api.user.userCreate(user)Nuances
- Uses
newgraphClientManagerfor token update (notnewgraphClient) - Does NOT update
_currentsignal — callsignIn()after creation - Username must be unique across the platform
- Throws on error with
debuggerfor development
signInWithX
OAuthInitiate Twitter/X OAuth2 authentication flow via redirect.
signInWithX(): Promise<void>Redirect URL Format
${baseUrl}/auth/provider/twitter2?token=${currentToken}&redirect_url=${origin}/start&r=${referrer}Behavior
- Reads referrer ID from
localStorage.getItem("r") - Full page redirect to Newgraph auth endpoint
- Returns to
/startwith JWT in URL params - Token extracted via
newsafe_jwtortokenURL param
signOut
AsyncClear the session, tokens, and local state.
signOut(): Promise<void>Behavior
- Clears API client token:
newgraphClient.updateToken("") - Clears
_currentsignal (empty object) - Waits 300ms for debouncing
- Calls
clearLocalState(false, false) - Clears
token.value.jwt
clearLocalState
AsyncClear local storage and IndexedDB cache, optionally preserving auth.
clearLocalState(
reboot: boolean = false,
keepAuth: boolean = true
): Promise<void>| Parameter | Type | Default | Description |
|---|---|---|---|
| reboot | boolean | false | Reload page after clearing (3s delay) |
| keepAuth | boolean | true | Preserve the auth token in localStorage |
Behavior
- Preserves
pre-signin-redirectlocalStorage key - Deletes IndexedDB cache via
cache.delete() - Useful for debugging stale data issues
Reactive Signals
Preact Signals for reactive auth state management.
tokenSignaltoken: Signal<SessionTokenResponse>
interface SessionTokenResponse {
jwt?: string; // Prefixed with "newsafe "
}Initialized from localStorage["newsafe-auth-token"] on module load.
_currentSignal_current: Signal<UserReadPrivateResponse>
// UserReadPrivateResponse includes:
// - id, username, displayName,
// - phone, email (private fields)
// - contentUrl (avatar), created, updatedThe current authenticated user. Empty object when not logged in.
winWindowwin = typeof window != "undefined"
? window
: { location: {} }SSR-safe window reference. Use for location.reload() etc.
Session Expiry
- JWT expires after 7 days
- Must refresh before expiry using
/auth/generateSessionToken - Check
token.config.expiresin JWT payload for expiry time - Implement token refresh in your app to maintain persistent sessions
User Types
UserReadPrivateResponse
Current user (has private fields)
{
id, username, displayName,
phone, // Private
email, // Private
contentUrl, // Avatar
watts, // Token balance
created, updated
}UserReadPublicResponse
Other users (public info only)
{
id, username, displayName,
contentUrl, // Avatar
watts, // Token balance
created, updated
// No phone/email
}Demo Widget
Example component showing auth state:
"use client";
import { _current, token } from "newgraph-signals/actions/auth";
import { Badge } from "@/components/ui/badge";
export function AuthStatusWidget() {
const user = _current.value;
const isLoggedIn = !!token.value.jwt;
return (
<div className="rounded-lg border p-4 space-y-2">
<div className="flex items-center gap-2">
<Badge variant={isLoggedIn ? "default" : "secondary"}>
{isLoggedIn ? "Authenticated" : "Not Logged In"}
</Badge>
</div>
{user?.username && (
<p className="text-sm">
Welcome, <strong>{user.username}</strong>
</p>
)}
{user?.id && (
<code className="text-xs text-muted-foreground block">
ID: {user.id}
</code>
)}
</div>
);
}