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

@mitkatadvisory/datasurfr-auth-utils

v1.0.1

Published

Shared auth, session management, and VAPT-compliant logout for Datasurfr apps

Readme

@mitkatadvisory/datasurfr-auth-utils

Shared auth, session management, and VAPT-compliant logout for all Datasurfr apps.

Drop this package into any app and get: token refresh, session heartbeat, cross-app logout, back-button protection, and a session-expired modal — all wired up in one config file.


Installation

npm install @mitkatadvisory/datasurfr-auth-utils

No .npmrc needed — published to public npm under @mitkatadvisory.


Quick Start (3 steps)

Step 1 — Create src/auth.config.ts

This is the only file your app needs to write. All logic lives in the package.

Vite + React app (Axios, sessionStorage tokens)

import {
  createRefreshAccessToken,
  createGetAuthHeaders,
  createAxiosAuthInterceptor,
  clearSession,
  storeTokens, getTokens, clearTokens, hasTokens,
  emitSessionExpired,
} from '@mitkatadvisory/datasurfr-auth-utils';
import api from './api/axiosInstance'; // your bare Axios instance

const BASE   = import.meta.env.VITE_API_INTERNAL_URL;
const LOGIN  = import.meta.env.VITE_LOGIN_URL ?? '/';
const KEY    = 'myapp_custom_auth'; // unique per app

type Tokens = { access_token: string; refresh_token: string };

export const storeAuthTokens = (t: Tokens)  => storeTokens(KEY, t as unknown as Record<string, unknown>);
export const getAuthTokens   = ()           => getTokens<Tokens>(KEY);
export const clearAuthTokens = ()           => clearTokens(KEY);
export const hasAuth         = ()           => hasTokens(KEY);

export const refreshAccessToken = createRefreshAccessToken({
  refreshEndpoint: `${BASE}/api/apps/auth/accessToken`,
  getRefreshToken: () => getAuthTokens()?.refresh_token ?? null,
  onNewToken: (t) => storeAuthTokens({ ...getAuthTokens()!, access_token: t }),
});

export const getAuthHeaders = createGetAuthHeaders({
  getAccessToken:  () => getAuthTokens()?.access_token ?? null,
  getRefreshToken: () => getAuthTokens()?.refresh_token ?? null,
  refreshAccessToken,
});

createAxiosAuthInterceptor(api, {
  getAuthHeaders,
  onAuthFailure: () => emitSessionExpired('expired'),
});

export const logout = (token?: string) => clearSession({
  accessToken: token,
  revokeEndpoint:       `${BASE}/api/apps/auth/revoke-token`,
  clearCookiesEndpoint: `${BASE}/api/apps/auth/clear-cookies`,
  loginUrl: LOGIN,
});

Next.js 15 App Router app (native fetch, httpOnly cookies)

import {
  createRefreshAccessToken,
  createGetAuthHeaders,
  createFetchWithAuth,
  clearSession,
  emitSessionExpired,
} from '@mitkatadvisory/datasurfr-auth-utils';
import { cookies } from 'next/headers'; // only Next.js-specific line

const BASE  = process.env.NEXT_PUBLIC_INTERNAL_API_BASE_URL!;
const LOGIN = process.env.NEXT_PUBLIC_REDIRECT_APPS_URL!;

export const refreshAccessToken = createRefreshAccessToken({
  refreshEndpoint: `${BASE}/api/apps/auth/accessToken`,
  getRefreshToken: async () => (await cookies()).get('refreshToken')?.value ?? null,
  onNewToken: () => {}, // server sets cookie via Set-Cookie header
});

export const getAuthHeaders = createGetAuthHeaders({
  getAccessToken:  async () => (await cookies()).get('accessToken')?.value ?? null,
  getRefreshToken: async () => (await cookies()).get('refreshToken')?.value ?? null,
  refreshAccessToken,
});

const onAuthFailure = () => emitSessionExpired('expired');

export const fetchInternal  = createFetchWithAuth({ baseUrl: BASE, getAuthHeaders, mode: 'json',     onAuthFailure });
export const fetchSse       = createFetchWithAuth({ baseUrl: BASE, getAuthHeaders, mode: 'stream',   onAuthFailure });
export const fetchFormData  = createFetchWithAuth({ baseUrl: BASE, getAuthHeaders, mode: 'formdata', onAuthFailure });

export const logout = (token?: string) => clearSession({
  accessToken: token,
  revokeEndpoint:       `${BASE}/api/apps/auth/revoke-token`,
  clearCookiesEndpoint: `${BASE}/api/apps/auth/clear-cookies`,
  loginUrl: LOGIN,
});

Step 2 — Wrap your app root with <SessionGuard>

<SessionGuard> provides all VAPT protections automatically with zero extra code.

Vite + React (src/main.tsx)

import { SessionGuard } from '@mitkatadvisory/datasurfr-auth-utils/react';
import { hasAuth } from './auth.config';

createRoot(document.getElementById('root')!).render(
  <SessionGuard loginUrl={import.meta.env.VITE_LOGIN_URL} isAuthenticated={hasAuth}>
    <App />
  </SessionGuard>
);

Next.js (src/app/layout.tsx)

import { SessionGuard } from '@mitkatadvisory/datasurfr-auth-utils/react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <SessionGuard
          loginUrl={process.env.NEXT_PUBLIC_REDIRECT_APPS_URL!}
          isAuthenticated={() => !!sessionStorage.getItem('myapp_auth_user')}
        >
          {children}
        </SessionGuard>
      </body>
    </html>
  );
}

Step 3 — Add session polling (optional but recommended)

// Vite + React: add in your root router/App component
import { useSessionValidator } from '@mitkatadvisory/datasurfr-auth-utils/react';
import { getAuthTokens, refreshAccessToken } from './auth.config';

useSessionValidator({
  getToken:            () => getAuthTokens()?.access_token ?? null,
  endpoint:            `${import.meta.env.VITE_API_INTERNAL_URL}/api/apps/userInfo`,
  refreshAccessToken,
  intervalMs:          60_000, // check every 60s
});

// Next.js: use heartbeat instead
import { useSessionHeartbeat } from '@mitkatadvisory/datasurfr-auth-utils/react';

useSessionHeartbeat({
  enabled:     !!token,
  endpoint:    '/api/user',
  intervalMs:  120_000,
});

Cross-App Logout

When a user logs out of any app, all open tabs across all apps are logged out automatically. No extra setup — clearSession() broadcasts and <SessionGuard> listens.

User clicks Logout in App A
        │
        ▼
clearSession() called
        ├─► broadcastLogout() ──► BroadcastChannel "auth_events"
        │                                  │
        │                     ┌────────────┴────────────┐
        │                     ▼                         ▼
        │               App B tab                 App C tab
        │          SessionGuard listener      SessionGuard listener
        │          clears storage             clears storage
        │          shows session-expired      shows session-expired
        │          modal                      modal
        │
        ├─► revokeToken()         (server, fire-and-forget)
        ├─► clearServerCookies()  (server, fire-and-forget)
        ├─► clearAllAuthStorage() (this tab)
        └─► safeRedirectToLogin() (replace — back button goes to login, not protected page)

Requirement: BroadcastChannel only works between tabs on the same browser origin. Both apps must be served from the same domain (e.g. app.example.com). If apps are on different subdomains, use a shared cookie logout signal instead.


Publishing a New Version

First-time setup (once per machine):

npm login   # use @mitkatadvisory org credentials

Every release:

# 1. Bump version (updates package.json + creates a git tag)
npm version patch   # bug fix: 1.0.0 → 1.0.1
npm version minor   # new feature: 1.0.0 → 1.1.0
npm version major   # breaking change: 1.0.0 → 2.0.0

# 2. Publish — prepublishOnly runs `npm run build` automatically
npm publish

# 3. Push version tag to git
git push && git push --tags

Installing the new version in a consuming app:

npm install @mitkatadvisory/datasurfr-auth-utils@latest

API Reference

Core (no React dependency)

| Export | Description | |--------|-------------| | isJwtExpired(token, bufferSecs?) | True if JWT exp within buffer (default 30s) or malformed | | SESSION_EXPIRED_EVENT | Event name constant: "session-expired" | | emitSessionExpired(reason) | Dispatch session-expired CustomEvent on window | | storeTokens(key, tokens) | Save object to sessionStorage under key | | getTokens<T>(key) | Read typed object from sessionStorage | | clearTokens(key) | Remove item from sessionStorage | | hasTokens(key) | True if key exists in sessionStorage | | clearAllAuthStorage() | Wipes both localStorage and sessionStorage | | safeRedirectToLogin(loginUrl, returnTo?) | Always uses replace() — no history entry | | callRefreshEndpoint(endpoint, refreshToken) | Raw HTTP POST to exchange refresh token | | createRefreshAccessToken(opts) | Factory: returns singleton-deduped refreshAccessToken() | | buildAuthHeaders(token) | { Authorization: 'Bearer ...', 'Content-Type': 'application/json' } | | createGetAuthHeaders(opts) | Factory: expiry-check + auto-refresh + returns { token, headers } | | createFetchWithAuth(opts) | Factory: fetch wrapper with 401-retry (modes: json / stream / formdata) | | createAxiosAuthInterceptor(axios, opts) | Attach 401-retry + 5xx-safe interceptor to Axios instance | | clearSession(opts) | Broadcast + revoke + clear cookies + clear storage + redirect | | broadcastLogout(source) | Post LOGOUT to BroadcastChannel auth_events | | broadcastLogin(source, tokens) | Post LOGIN to BroadcastChannel | | listenForCrossAppLogout(onLogout) | Subscribe to LOGOUT broadcasts; returns cleanup fn | | listenForCrossAppLogin(onLogin) | Subscribe to LOGIN broadcasts; returns cleanup fn |

React (@mitkatadvisory/datasurfr-auth-utils/react)

| Export | Description | |--------|-------------| | createAuthProvider(opts) | Factory: returns { AuthProvider, useAuth } | | <SessionGuard> | Root wrapper: bfcache guard + no-store meta + cross-app logout + modal | | <SessionExpiredModal> | Non-dismissible session-expired overlay | | useBfcacheGuard(opts) | VAPT: redirect on browser back after logout | | useSessionHeartbeat(opts) | Poll endpoint every N ms; pause on tab hidden | | useSessionValidator(opts) | Validate token on interval + focus + visibility change | | useLogout(opts) | Returns { handleLogout, loading, error } |


VAPT Compliance

What this package handles automatically (zero app code needed):

| Threat | How it's handled | |--------|-----------------| | Back button shows auth content after logout | useBfcacheGuard inside <SessionGuard> | | Protected page in history after logout | safeRedirectToLogin uses replace(), never href | | Browser caches authenticated pages | <meta no-store> injected by <SessionGuard> | | Tokens left in storage after logout | clearAllAuthStorage() inside clearSession() | | Other tabs/apps stay logged in | broadcastLogout() in clearSession() + listener in <SessionGuard> | | Concurrent token refresh race | Singleton dedup in createRefreshAccessToken() |

What your server must do:

  • POST /api/apps/auth/revoke-token — invalidate the refresh token in the database
  • POST /api/apps/auth/clear-cookies — clear httpOnly session cookies from the response