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 user
  • signIn — Exchange Firebase token for Newgraph session
  • signOut — Clear session and cache
  • create — Create new user account

OAuth & Utilities

  • signInWithX — Twitter/X OAuth flow
  • clearLocalState — Reset local data
  • getOnesignalAuthHash — Get OneSignal hash

Reactive Signals

  • token — JWT Signal (newsafe {jwt})
  • _current — Current user Signal
  • win — Safe window reference

Storage Keys

  • newsafe-auth-token — JWT in localStorage
  • pre-signin-redirect — Redirect URL after auth
  • r — 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}`)
Critical: Token Format

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

EndpointMethodPurpose
/auth/generateSessionTokenPOSTExchange Firebase token for Newgraph JWT
/auth/onesignalGETGet OneSignal external ID auth hash
/auth/provider/twitter2GETInitiate Twitter/X OAuth2 flow
/userGETGet current authenticated user
/userPOSTCreate new user account

current

ProgressiveHandler

Fetch the current authenticated user's private profile. Uses the stored JWT to authenticate.

current(params?: { autostart: boolean })
  : ProgressiveHandlerResponse<UserReadPrivateResponse>
ParameterTypeDefaultDescription
autostartbooleanfalseWhether 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 _current Signal reactively
  • Tracks execution count via _currentShared.executions
  • Also exported as currentPassive without 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

Async

Exchange a Firebase ID token for a Newgraph session JWT and establish the session.

signIn(
  newgraphToken: string,
  opts?: { invitorId?: string }
): Promise<string>
ParameterTypeRequiredDescription
newgraphTokenstringYesFirebase ID token from phone/OAuth auth
opts.invitorIdstringNoReferrer 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

  1. Sets Firebase token on client: newgraphClient.updateToken(firebaseToken)
  2. Calls /auth/generateSessionToken to exchange for JWT
  3. Stores JWT in localStorage: newsafe-auth-token
  4. Updates client with prefixed token: newsafe {jwt}
  5. Fetches current user and caches to IndexedDB
  6. Updates _current signal 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

Async

Create a new user account with a Firebase token.

create(
  firebaseToken: string,
  user: Partial<UserReadPrivateResponse>
): Promise<UserReadPrivateResponse>
ParameterTypeRequiredDescription
firebaseTokenstringYesFirebase ID token after phone verification
user.usernamestringNoUnique username
user.displayNamestringNoDisplay name shown in UI

API Endpoint

api.user.userCreate(user)

Nuances

  • Uses newgraphClientManager for token update (not newgraphClient)
  • Does NOT update _current signal — call signIn() after creation
  • Username must be unique across the platform
  • Throws on error with debugger for development

signInWithX

OAuth

Initiate 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 /start with JWT in URL params
  • Token extracted via newsafe_jwt or token URL param

signOut

Async

Clear the session, tokens, and local state.

signOut(): Promise<void>

Behavior

  1. Clears API client token: newgraphClient.updateToken("")
  2. Clears _current signal (empty object)
  3. Waits 300ms for debouncing
  4. Calls clearLocalState(false, false)
  5. Clears token.value.jwt

clearLocalState

Async

Clear local storage and IndexedDB cache, optionally preserving auth.

clearLocalState(
  reboot: boolean = false,
  keepAuth: boolean = true
): Promise<void>
ParameterTypeDefaultDescription
rebootbooleanfalseReload page after clearing (3s delay)
keepAuthbooleantruePreserve the auth token in localStorage

Behavior

  • Preserves pre-signin-redirect localStorage key
  • Deletes IndexedDB cache via cache.delete()
  • Useful for debugging stale data issues

Reactive Signals

Preact Signals for reactive auth state management.

tokenSignal
token: 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, updated

The current authenticated user. Empty object when not logged in.

winWindow
win = typeof window != "undefined" 
  ? window 
  : { location: {} }

SSR-safe window reference. Use for location.reload() etc.

Session Expiry

Important: Token Lifetime
  • JWT expires after 7 days
  • Must refresh before expiry using /auth/generateSessionToken
  • Check token.config.expires in 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>
  );
}

Related