Skip to Content
Auth

@gg-software/auth

Auth / session / permission logic. The main entry is pure — no React; the React helpers live in the optional @gg-software/auth/react entry.

pnpm add @gg-software/auth
import { decodeJwt, getSession, can } from '@gg-software/auth'; import { AuthProvider, useAuth } from '@gg-software/auth/react'; // optional, needs react

Token

parseBearerToken

Extract the token from an HTTP Authorization header value. Matching is case-insensitive on the Bearer prefix and tolerant of surrounding whitespace.

parseBearerToken('Bearer abc123'); // "abc123" parseBearerToken('Basic abc123'); // null parseBearerToken(undefined); // null
ParameterTypeDescription
headerstring | null | undefinedthe raw header value
returnsstring | nullthe token, or null if missing / not a Bearer token

decodeJwt

Decode a JWT’s payload without verifying the signature. Use it to read claims client-side (expiry, user id) — never to trust a token; verification belongs on the server.

decodeJwt(token); // { sub: "42", exp: 1767225600, ... } decodeJwt('not-a-jwt'); // null decodeJwt<{ sub: string }>(token)?.sub; // typed access
ParameterTypeDescription
tokenstring | null | undefinedthe raw JWT (header.payload.signature)
returnsT | nulldecoded payload (default type JwtPayload), or null when malformed
type JwtPayload = { iss?: string; sub?: string; aud?: string | string[]; exp?: number; // seconds since epoch nbf?: number; iat?: number; jti?: string; [claim: string]: unknown; };

isTokenExpired

Check the exp claim against the current time. A missing/malformed token counts as expired (unusable); a valid token without exp counts as not expired.

isTokenExpired(freshToken); // false isTokenExpired(oldToken); // true isTokenExpired(oldToken, 60); // false within 60s of expiry (clock-skew leeway) isTokenExpired(null); // true
ParameterTypeDefaultDescription
tokenstring | null | undefinedthe raw JWT
leewaySecondsnumber0token stays valid this many seconds past exp
returnsboolean

getTokenExpiry

Read the exp claim as a Date.

getTokenExpiry(token); // Date | null
ParameterTypeDescription
tokenstring | null | undefinedthe raw JWT
returnsDate | nullexpiry, or null when malformed / no exp

Session

Sessions are stored as JSON in localStorage under the key "gg-session" by default. Every function takes optional SessionOptions to change that; all of them are SSR-safe (no-ops / null when window is unavailable).

type AuthSession = { token?: string; user?: AuthUser; [key: string]: unknown; // extend freely }; type AuthUser = { id?: string | number; name?: string; email?: string; roles?: string[]; permissions?: string[]; [key: string]: unknown; }; type SessionOptions = { key?: string; // storage key (default "gg-session") storage?: Storage; // storage backend (default window.localStorage) };

getSession

const session = getSession(); if (session?.token) fetchWithAuth(session.token); // custom key / sessionStorage getSession({ key: 'admin-session', storage: sessionStorage });
ParameterTypeDescription
optionsSessionOptions?key / storage overrides
returnsT | nullthe stored session (default type AuthSession), or null

setSession

Persist the session and notify onSessionChange subscribers.

setSession({ token, user: { id: 42, name: 'Ada', roles: ['admin'] } });
ParameterTypeDescription
sessionT extends AuthSessionthe session to persist
optionsSessionOptions?key / storage overrides

clearSession

Wipe the session (log out) and notify subscribers.

clearSession();

onSessionChange

Subscribe to session changes — fires on setSession / clearSession in this tab and on changes from other browser tabs (via the storage event). Returns an unsubscribe function.

const stop = onSessionChange((session) => { if (!session) redirectToLogin(); }); // later stop();
ParameterTypeDescription
callback(session: T | null) => voidcalled with the new session (or null)
optionsSessionOptions?key / storage overrides
returns() => voidunsubscribe

RBAC

Role / permission checks. They accept anything with optional roles / permissions string arrays (RbacUser — the session’s AuthUser fits), and treat null / undefined users as having nothing.

Permission format: "action:subject" strings, e.g. "read:orders". Granted permissions may use wildcards: "*" grants everything, "read:*" any subject, "*:orders" any action. Non-namespaced permissions (no :) only match exactly.

hasRole

hasRole(user, 'admin'); // user.roles includes "admin" hasRole(null, 'admin'); // false

hasAnyRole

hasAnyRole(user, ['admin', 'editor']); // at least one of them

hasPermission

// user.permissions = ['read:*', 'write:orders'] hasPermission(user, 'read:customers'); // true (read:*) hasPermission(user, 'write:orders'); // true (exact) hasPermission(user, 'delete:orders'); // false

can

Policy-style shorthand: can(user, action, subject)hasPermission(user, `${action}:${subject}`).

can(user, 'delete', 'orders'); // checks "delete:orders"
FunctionSignature
hasRole(user: RbacUser | null | undefined, role: string) => boolean
hasAnyRole(user: RbacUser | null | undefined, roles: string[]) => boolean
hasPermission(user: RbacUser | null | undefined, permission: string) => boolean
can(user: RbacUser | null | undefined, action: string, subject: string) => boolean

React helpers (@gg-software/auth/react)

Optional entry — requires react (optional peer dependency; ships with 'use client', so it works under the Next.js App Router).

AuthProvider

Provides the current session/user to the tree. Reads the persisted session after mount (SSR-safe) and stays in sync with setSession / clearSession calls anywhere in the app — including other tabs.

import { AuthProvider } from '@gg-software/auth/react'; <AuthProvider> <App /> </AuthProvider>;
PropTypeDefaultDescription
children (required)ReactNodecontent rendered inside the component
storageKeystring"gg-session"storage key for the persisted session

useAuth

Access the session plus login / logout. Throws outside an <AuthProvider>.

import { useAuth } from '@gg-software/auth/react'; function UserMenu() { const { user, isAuthenticated, login, logout } = useAuth(); if (!isAuthenticated) return <LoginButton onDone={(session) => login(session)} />; return <button onClick={logout}>Sign out {user?.name}</button>; }

Returned AuthContextValue:

FieldTypeDescription
sessionAuthSession | nullthe persisted session
userAuthUser | nullshortcut for session.user
isAuthenticatedbooleansession != null
login(session) => voidpersist a session (storage + context)
logout() => voidwipe the session (storage + context)

RequireAuth

Gate an area behind authentication.

import { RequireAuth } from '@gg-software/auth/react'; <RequireAuth fallback={<LoginPage />}> <AdminArea /> </RequireAuth>;
PropTypeDefaultDescription
children (required)ReactNoderendered when authenticated
fallbackReactNodenullrendered when there is no session

usePermission

Boolean permission check against the current user (wildcards respected, see hasPermission).

import { usePermission } from '@gg-software/auth/react'; function DeleteButton() { const canDelete = usePermission('delete:orders'); return canDelete ? <Button danger>Delete</Button> : null; }