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

@cloudwick/acams-auth-client

v1.0.0

Published

ACAMS authentication client library for React applications with Context API and localStorage persistence

Readme

ACAMS Auth Provider

React authentication provider for ACAMS (Amorphic Central Authentication Management System). Handles silent SSO via iframe bridge and redirect-based login.

Installation

npm install @acams/auth-provider
# or
yarn add @acams/auth-provider

Quick Start

import { AcamsAuthProvider, AuthGuard } from "@acams/auth-provider";

function App() {
  return (
    <AcamsAuthProvider
      acamsApiGateway="https://api.acams.example.com"
      appId="your-app-id"
      redirectUri={window.location.origin + "/auth/callback"}
      acamsBridgeUrl="https://acams.example.com/bridge"
      acamsLoginUrl="https://acams.example.com/login"
      onLoginSuccess={(user) => console.log("Logged in:", user.email)}
      onSessionExpired={() => console.log("Session expired")}
    >
      <AuthGuard fallback={<div>Authenticating...</div>} loader={<Spinner />}>
        <Dashboard />
      </AuthGuard>
    </AcamsAuthProvider>
  );
}

That's it. The provider handles everything automatically.


Authentication Flow

How It Works

  1. App loads → Provider hydrates state from localStorage
  2. If valid tokens exist → User is authenticated immediately
  3. If no tokens → Provider checks iframe bridge for existing ACAMS session
  4. If ACAMS session found → Silent login via POST auth/prepare with silent: true
  5. If no session / bridge timeout → Redirect to acamsLoginUrl
  6. After ACAMS login → ACAMS redirects back with code and state params
  7. Provider auto-handles callback → Exchanges code for tokens, stores them
  8. Session validated → User is authenticated, session auto-validates every 10 minutes

Flow Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│ App Loads                                                                   │
└─────────────────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Provider hydrates state from localStorage                                   │
│ - If valid tokens exist → isAuthenticated = true                            │
└─────────────────────────────────────────────────────────────────────────────┘
         │
         ▼ (no tokens)
┌─────────────────────────────────────────────────────────────────────────────┐
│ Check iframe bridge for existing ACAMS session                              │
│ - Bridge responds → startSilentAuth() → redirect to ACAMS                   │
│ - Bridge timeout → redirect to acamsLoginUrl                                │
└─────────────────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ ACAMS redirects back with ?code=...&state=...                               │
│ Provider auto-detects and exchanges for tokens                              │
└─────────────────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ User is authenticated                                                       │
│ - Tokens stored in localStorage                                             │
│ - Session auto-validates every 10 minutes (configurable)                    │
└─────────────────────────────────────────────────────────────────────────────┘

Integration Guide

Step 1: Wrap Your App

// src/main.tsx
import { AcamsAuthProvider, AuthGuard } from "@acams/auth-provider";

function App() {
  return (
    <AcamsAuthProvider
      acamsApiGateway={import.meta.env.VITE_ACAMS_API_URL}
      appId={import.meta.env.VITE_ACAMS_APP_ID}
      redirectUri={`${window.location.origin}/auth/callback`}
      acamsBridgeUrl={import.meta.env.VITE_ACAMS_BRIDGE_URL}
      acamsLoginUrl={import.meta.env.VITE_ACAMS_LOGIN_URL}
      onLoginSuccess={(user, tokens) => {
        console.log("Login success:", user.email);
      }}
      onSessionExpired={() => {
        console.log("Session expired");
      }}
      onAuthBridgeFailed={() => {
        console.log("No ACAMS session, redirecting to login...");
      }}
      onError={(error, context) => {
        console.error(`Auth error in ${context}:`, error);
      }}
    >
      <AuthGuard fallback={<LoadingScreen />}>
        <YourApp />
      </AuthGuard>
    </AcamsAuthProvider>
  );
}

Step 2: Access Auth State (Optional)

Use useAcamsAuth hook anywhere inside the provider to access auth state.

import { useAcamsAuth } from "@acams/auth-provider";

function Header() {
  const { isAuthenticated, user, logout } = useAcamsAuth();

  if (!isAuthenticated) return null;

  return (
    <header>
      <span>Welcome, {user.email}</span>
      <button onClick={logout}>Logout</button>
    </header>
  );
}

Step 3: Manual Session Validation (Optional)

Use useSessionValidator if you need to manually trigger validation.

import { useSessionValidator } from "@acams/auth-provider";

function Dashboard() {
  const { validateNow, lastValidatedAt } = useSessionValidator();

  return (
    <div>
      <p>Last validated: {lastValidatedAt}</p>
      <button onClick={validateNow}>Validate Now</button>
    </div>
  );
}

API Reference

AcamsAuthProvider Props

| Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | acamsApiGateway | string | ✅ | - | Base URL of the ACAMS API gateway | | appId | string | ✅ | - | Your application ID registered with ACAMS | | redirectUri | string | ✅ | - | OAuth callback URL | | acamsBridgeUrl | string | ❌ | - | URL of ACAMS iframe bridge for session detection | | acamsLoginUrl | string | ❌ | - | URL to redirect when no session exists | | bridgeTimeout | number | ❌ | 3000 | Max wait time for bridge response (ms) | | enableAuthBridge | boolean | ❌ | true | Enable iframe bridge SSO | | validationInterval | number | ❌ | 600000 | Session validation interval (ms) | | enableAutoValidation | boolean | ❌ | true | Auto-validate session periodically | | onLoginStart | (email) => void | ❌ | - | Fired when auth flow begins | | onLoginSuccess | (user, tokens) => void | ❌ | - | Fired on successful authentication | | onLoginError | (error) => void | ❌ | - | Fired on auth failure | | onLogout | () => void | ❌ | - | Fired after logout | | onTokenRenewed | (tokens) => void | ❌ | - | Fired when tokens refreshed | | onTokenRenewalError | (error) => void | ❌ | - | Fired on token refresh failure | | onSessionExpired | () => void | ❌ | - | Fired when session expires | | onSessionValidated | (isValid) => void | ❌ | - | Fired after validation check | | onStateHydrated | (state) => void | ❌ | - | Fired after localStorage hydration | | onStateChange | (state, prev) => void | ❌ | - | Fired on any state change | | onError | (error, context) => void | ❌ | - | General error handler | | onAuthBridgeFailed | () => void | ❌ | - | Fired when bridge times out |

useAcamsAuth Hook

const {
  isAuthenticated,  // boolean - true if user has valid session
  isLoading,        // boolean - true during auth operations
  validSession,     // boolean - session validity flag
  user,             // UserInfo - { userId, email, sessionId }
  tokens,           // TokenSet | null - { idToken, opaqueToken, accessToken }
  startLogin,       // (email: string, idpHint?: string) => Promise<PrepareAuthResponse>
  startSilentLogin, // (email?: string) => Promise<void>
  handleCallback,   // (code: string, state: string) => Promise<void>
  logout,           // () => void
  forceTokenRenewal // () => Promise<boolean>
} = useAcamsAuth();

useSessionValidator Hook

const {
  validateNow,      // () => Promise<boolean> - manually validate session
  lastValidatedAt,  // string | null - ISO timestamp of last validation
  isValidating      // boolean - true during validation
} = useSessionValidator();

AuthGuard Component

<AuthGuard
  fallback={<LoginPage />}    // Shown when not authenticated
  loader={<LoadingSpinner />} // Shown while loading (optional)
>
  <ProtectedContent />
</AuthGuard>

Types

AuthState

interface AuthState {
  idToken: string | null;
  opaqueToken: string | null;
  accessToken: string | null;
  sessionActive: boolean;
  validSession: boolean;
  sessionId: string | null;
  userId: string | null;
  email: string | null;
  lastValidatedAt: string | null;
  isLoading: boolean;
}

TokenSet

interface TokenSet {
  idToken: string;
  opaqueToken: string;
  accessToken?: string;
}

UserInfo

interface UserInfo {
  userId: string | null;
  email: string | null;
  sessionId: string | null;
}

Features

Auto OAuth Callback Handling

Provider automatically detects code and state URL params and exchanges them for tokens. No need to create a separate callback route.

Cross-Tab Sync

Auth state syncs across browser tabs via localStorage events. Login/logout in one tab updates all others instantly.

Token Renewal

HTTP client automatically renews tokens on 401/403 responses. Failed renewal triggers onSessionExpired.

Storage

Auth state persists in localStorage under key acams_auth_state.


Complete Example

// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { AcamsAuthProvider, AuthGuard, useAcamsAuth } from "@acams/auth-provider";

function Dashboard() {
  const { user, logout } = useAcamsAuth();
  return (
    <div>
      <h1>Welcome, {user.email}</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

function LoadingScreen() {
  return <div>Authenticating with ACAMS...</div>;
}

function App() {
  return (
    <AcamsAuthProvider
      acamsApiGateway={import.meta.env.VITE_ACAMS_API_URL}
      appId={import.meta.env.VITE_ACAMS_APP_ID}
      redirectUri={`${window.location.origin}/auth/callback`}
      acamsBridgeUrl={import.meta.env.VITE_ACAMS_BRIDGE_URL}
      acamsLoginUrl={import.meta.env.VITE_ACAMS_LOGIN_URL}
      onLoginSuccess={(user) => console.log("Welcome:", user.email)}
      onSessionExpired={() => console.log("Session expired")}
      onError={(err, ctx) => console.error(`[${ctx}]`, err)}
    >
      <AuthGuard fallback={<LoadingScreen />}>
        <Dashboard />
      </AuthGuard>
    </AcamsAuthProvider>
  );
}

ReactDOM.createRoot(document.getElementById("root")!).render(<App />);

License

MIT