npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@iot-platform-saf/auth-client

v1.1.1

Published

Auth utilities for IoT Unified Platform — server-side JWT verification, client AuthProvider, and route handler factory

Readme

@iot-platform-saf/auth-client

Auth utilities for the Safaricom IoT Unified Platform. Provides server-side JWT verification, a client-side AuthProvider with session polling and idle timeout, and a route handler factory for the /api/auth/session endpoint.

Architecture note: The shell (iot-unified-platform) owns authentication — it creates and signs JWTs. Child apps only verify tokens. This package never creates a JWT.


Requirements

| Peer dependency | Version | |---|---| | next | >= 16 | | react | >= 19 | | zustand | >= 5 | | react-idle-timer | >= 5 |

Install peer dependencies if not already present:

pnpm add next react react-dom zustand react-idle-timer

Installation

pnpm add @iot-platform-saf/auth-client

Environment Variables

Add these to .env.local in your app. Values must match the shell exactly.

# Secret used to sign and verify JWTs — must match the shell's AUTH_SECRET
AUTH_SECRET=your-secret-here

# Name of the session cookie set by the shell (default: iot-platform-session)
NEXT_PUBLIC_AUTH_COOKIE_NAME=iot-platform-session

# Base URL of the shell app (used for cross-app redirects)
NEXT_PUBLIC_SHELL_URL=http://localhost:3000

Step-by-Step Setup

1. Configure the auth client

Create lib/auth-client.ts — call createAuthClient() once with your app's session URL. Export the three items it returns so the rest of the app can import from a single place.

// lib/auth-client.ts
import { createAuthClient } from "@iot-platform-saf/auth-client";

export const { AuthProvider, useAuth, useAuthStore } = createAuthClient({
  sessionUrl: "/sim-platform/api/auth/session", // your app's basePath + /api/auth/session
  logoutUrl: "/api/auth/logout",                // always the shell's logout route
});

2. Re-export server utilities

Create lib/auth.ts — thin re-export so server code never imports directly from the package path:

// lib/auth.ts
export {
  verifySessionToken,
  getSessionCookieName,
  getShellLoginUrl,
  createSessionRoute,
} from "@iot-platform-saf/auth-client/server";

3. Re-export hooks

Create hooks/use-auth.ts — lets feature code import from a consistent local path:

// hooks/use-auth.ts
export { useAuth, useAuthStore } from "@/lib/auth-client";

4. Create the session route

Create app/api/auth/session/route.ts — this is the endpoint AuthProvider polls on mount:

// app/api/auth/session/route.ts
import { createSessionRoute } from "@/lib/auth";

export const GET = createSessionRoute();

createSessionRoute() reads AUTH_SECRET and NEXT_PUBLIC_AUTH_COOKIE_NAME at request time, verifies the shell cookie, and returns { user } or { user: null } with status 401.

5. Create the AuthProvider wrapper

Create providers/auth-provider.tsx — a thin wrapper that passes your app's expiry dialog into the package's AuthProvider:

// providers/auth-provider.tsx
"use client";

import { AuthProvider as PkgAuthProvider } from "@/lib/auth-client";
import { SessionExpiryDialog } from "@/components/common/dialogs/session-expiry-dialog";
import type { ReactNode } from "react";

export function AuthProvider({ children }: { children: ReactNode }) {
  return (
    <PkgAuthProvider
      renderExpiryWarning={({ secondsRemaining, onStaySignedIn, onSignOut }) => (
        <SessionExpiryDialog
          open
          secondsRemaining={secondsRemaining}
          onStaySignedIn={onStaySignedIn}
          onSignOut={onSignOut}
        />
      )}
    >
      {children}
    </PkgAuthProvider>
  );
}

To disable the idle timeout on a specific layout (e.g. a public page):

<PkgAuthProvider idleTimeout={false}>{children}</PkgAuthProvider>

6. Wire up in the app layout

// app/(app)/layout.tsx
import { AuthProvider } from "@/providers/auth-provider";

export default function AppLayout({ children }: { children: React.ReactNode }) {
  return (
    <AuthProvider>
      {/* AppNavbar, AppSidebar, main, AppFooter */}
      {children}
    </AuthProvider>
  );
}

API Reference

Client entry point — @iot-platform-saf/auth-client

createAuthClient(config?)

Factory function. Call once per app in lib/auth-client.ts.

const { AuthProvider, useAuth, useAuthStore } = createAuthClient({
  sessionUrl?: string,  // default: "/api/auth/session"
  logoutUrl?: string,   // default: "/api/auth/logout"
});

Returns three items scoped to this client instance (not global singletons):

| Export | Type | Description | |---|---|---| | AuthProvider | React component | Fetches session on mount, polls every 30 min, drives idle timeout | | useAuth | Hook | Returns { user, isAuthenticated, setUser, logout } | | useAuthStore | Zustand store | Direct store access — use for authLoading in navbar skeleton |


AuthProvider props

| Prop | Type | Default | Description | |---|---|---|---| | children | ReactNode | — | App content | | idleTimeout | boolean | true | Set to false to disable idle detection on public layouts | | renderExpiryWarning | (props) => ReactNode | — | Render the expiry warning UI; receives secondsRemaining, onStaySignedIn, onSignOut |

What AuthProvider does on mount:

  1. Fetches sessionUrl to populate the Zustand store
  2. Sets authLoading: true while fetching, false when done
  3. Polls sessionUrl every 30 minutes (while a user is logged in) to detect expired 8-hour JWTs
  4. Starts the idle timer — calls onExpire after 15 minutes of inactivity (warns at 13 minutes)

useAuth()

const { user, isAuthenticated, setUser, logout } = useAuth();

| Return | Type | Description | |---|---|---| | user | User \| null | Current user from the Zustand store | | isAuthenticated | boolean | true when user is non-null | | setUser | (user: User \| null) => void | Update the store (e.g. after a profile edit) | | logout | () => Promise<void> | POSTs to logoutUrl, clears the store, redirects to / |


useAuthStore

Zustand store — use directly only when you need authLoading (e.g. the navbar loading skeleton):

const authLoading = useAuthStore((s) => s.authLoading);

| Field | Type | Description | |---|---|---| | user | User \| null | Current user | | isAuthenticated | boolean | Derived from user | | authLoading | boolean | true while the initial session fetch is in flight | | setUser | action | — | | clearUser | action | Resets user + isAuthenticated | | setAuthLoading | action | — |


useIdleTimeout(options)

Used internally by AuthProvider. Exposed for advanced use cases.

const { isWarning, secondsRemaining, extendSession } = useIdleTimeout({
  timeoutMs?: number,        // default: 15 minutes
  warningBeforeMs?: number,  // default: 2 minutes
  enabled?: boolean,         // default: true
  onExpire: () => void,      // called when the idle timer expires
});

Cross-tab sync is enabled via react-idle-timer's BroadcastChannel (name: "iot-platform-idle"). Activity in any open tab resets every tab's timer.


Server entry point — @iot-platform-saf/auth-client/server

Import from the /server subpath in Server Components and Route Handlers. Never import this in client components.

verifySessionToken(token)

const user: User | null = await verifySessionToken(token);

Verifies the iot-platform-session JWT using AUTH_SECRET. Returns the decoded User or null for expired, malformed, or unsigned tokens.

getSessionCookieName()

const name: string = getSessionCookieName();
// returns process.env.NEXT_PUBLIC_AUTH_COOKIE_NAME ?? "iot-platform-session"

getShellLoginUrl(redirectPath?)

getShellLoginUrl("/sim-platform/billing");
// returns "/login?redirect=%2Fsim-platform%2Fbilling"

getShellLoginUrl();
// returns "/login"

Build a redirect-aware login URL. Use in middleware.ts or server error boundaries.

createSessionRoute()

// app/api/auth/session/route.ts
import { createSessionRoute } from "@/lib/auth";
export const GET = createSessionRoute();

Factory for the GET /api/auth/session route handler. Reads AUTH_SECRET and NEXT_PUBLIC_AUTH_COOKIE_NAME at request time. Returns:

  • 200 { user: User } — valid session
  • 401 { user: null } — missing or invalid cookie

How the Pieces Fit Together

shell proxy
  → validates iot-platform-session cookie
  → forwards request to child app

child app (on page load)
  AuthProvider mounts
    → GET /{basePath}/api/auth/session          (sessionUrl)
        ↓
      createSessionRoute() reads cookie
        → verifySessionToken(token)             (AUTH_SECRET)
        → returns { user }
        ↓
      AuthProvider.setUser(user)
      AuthProvider.setAuthLoading(false)
        ↓
      Page renders with real user data

every 30 min (background poll)
  → same flow, detects expired 8-hour JWTs

after 15 min idle
  → renderExpiryWarning fires (2-min countdown)
  → on expire: POST /api/auth/logout → redirect /login

TypeScript

All exports are fully typed. Import types directly from the package:

import type {
  // Platform domain types
  User,
  UserRole,
  Account,
  // Auth types
  AuthClientConfig,
  AuthProviderProps,
  AuthStore,
  ExpiryWarningProps,
  UseIdleTimeoutOptions,
  UseIdleTimeoutResult,
} from "@iot-platform-saf/auth-client";

Platform types

@iot-platform-saf/auth-client owns the core user and account types used across the platform. Import them from here whenever you need them in auth-related code; for UI code import from @iot-platform-saf/ui-shell instead.

| Type | Description | |---|---| | User | Authenticated user — id, email, name, role, account, and optional profile fields | | UserRole | "admin" \| "editor" \| "viewer" | | Account | Organisation account — id, name, displayName |