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

@authrim/web

v0.1.26

Published

Browser implementation for Authrim SDK

Readme

@authrim/web

Browser SDK for Authrim - a modern, developer-friendly Identity Provider.

npm version License

Overview

@authrim/web is a browser-specific authentication SDK that provides:

  • Unified { data, error } response pattern - Type-safe error handling with discriminated unions
  • Direct Auth - Passkey (WebAuthn), Email Code, Social Login
  • OAuth/OIDC - Popup, Silent Auth, Redirect flows
  • Session Management - check_session_iframe, Session Monitor, Front-Channel Logout
  • Device Flow UI - Helper for CLI/TV/IoT authentication

This package uses @authrim/core internally and provides browser-specific implementations.

Installation

npm / pnpm / yarn

npm install @authrim/web
# or
pnpm add @authrim/web
# or
yarn add @authrim/web

Note: @authrim/core is included as a dependency and will be installed automatically.

CDN (UMD)

The UMD build includes @authrim/core bundled, so no additional scripts are needed.

<!-- Latest version (includes @authrim/core) -->
<script src="https://unpkg.com/@authrim/web/dist/authrim-web.umd.global.js"></script>

<!-- Or via jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@authrim/web/dist/authrim-web.umd.global.js"></script>

<script>
  // Global variable: AuthrimWeb
  (async () => {
    const auth = await AuthrimWeb.createAuthrim({
      issuer: 'https://auth.example.com',
      clientId: 'your-client-id',
    });

    document.getElementById('login').onclick = async () => {
      const { data, error } = await auth.passkey.login();
      if (error) {
        alert(error.message);
        return;
      }
      console.log('Logged in:', data.user);
    };
  })();
</script>

ES Modules (CDN)

<script type="module">
  import { createAuthrim } from 'https://unpkg.com/@authrim/web/dist/index.js';

  const auth = await createAuthrim({
    issuer: 'https://auth.example.com',
    clientId: 'your-client-id',
  });
</script>

Quick Start

Basic Usage (Direct Auth)

import { createAuthrim } from '@authrim/web';

const auth = await createAuthrim({
  issuer: 'https://auth.example.com',
  clientId: 'your-client-id',
});

// Passkey login
const { data, error } = await auth.passkey.login();
if (error) {
  console.error('Login failed:', error.message);
  return;
}
console.log('Welcome!', data.user.email);

// Sign out
await auth.signOut();

With OAuth Features

const auth = await createAuthrim({
  issuer: 'https://auth.example.com',
  clientId: 'your-client-id',
  enableOAuth: true, // Enable OAuth namespace
});

// OAuth popup login
const { data, error } = await auth.oauth.popup.login({
  scopes: ['openid', 'profile', 'email'],
});

if (data) {
  console.log('Access Token:', data.accessToken);
}

Feature List

| Category | Feature | Description | |----------|---------|-------------| | Direct Auth | | | | | Passkey (WebAuthn) | Login, SignUp, Register | | | Passkey Conditional UI | Autofill integration | | | Email Code (OTP) | Send and verify codes | | | Social Login | Popup and redirect flows | | OAuth/OIDC | | | | | Authorization Code + PKCE | Standard secure flow | | | Silent Auth | Hidden iframe session renewal | | | Popup Auth | Popup window flow | | | Redirect Auth | Full page redirect flow | | Session Management | | | | | CheckSessionIframeManager | postMessage-based session check | | | SessionMonitor | Periodic session polling with events | | | FrontChannelLogoutHandler | Handle logout requests from OP | | Device Flow | | | | | DeviceFlowUI | Events, countdown, QR code helpers | | | formatUserCode | Format user code for display | | | getDeviceFlowQRCodeUrl | Get URL for QR code | | Utilities | | | | | Event System | Auth lifecycle events | | | Response Pattern | Unified { data, error } format |


Using with @authrim/core

@authrim/web uses @authrim/core internally. For advanced use cases, you can import from both:

import { createAuthrim, SessionMonitor, FrontChannelLogoutHandler } from '@authrim/web';
import {
  // JWT Utilities
  decodeJwt,
  decodeIdToken,
  isJwtExpired,
  getIdTokenNonce,

  // Base64url
  base64urlEncode,
  base64urlDecode,
  stringToBase64url,
  base64urlToString,

  // Security
  timingSafeEqual,

  // Session Management (server-side helpers)
  SessionStateCalculator,
  FrontChannelLogoutUrlBuilder,
  BackChannelLogoutValidator,
  BACKCHANNEL_LOGOUT_EVENT,

  // Types
  type TokenSet,
  type OIDCDiscoveryDocument,
  type UserInfo,
} from '@authrim/core';

// Use web SDK for browser auth
const auth = await createAuthrim({
  issuer: 'https://auth.example.com',
  clientId: 'your-client-id',
});

// Use core utilities
const decoded = decodeIdToken(idToken);
const isExpired = isJwtExpired(accessToken);

When to use @authrim/core directly

| Use Case | Package | |----------|---------| | Browser SPA/PWA | @authrim/web | | Server-side Node.js | @authrim/core with custom providers | | Cloudflare Workers | @authrim/core with Workers providers | | Custom platforms | @authrim/core with your own providers | | Utility functions only | @authrim/core |


API Reference

Configuration

interface AuthrimConfig {
  /** Authrim IdP URL */
  issuer: string;
  /** OAuth client ID */
  clientId: string;
  /** Enable OAuth/OIDC features (default: false) */
  enableOAuth?: boolean;
  /** Storage configuration */
  storage?: {
    type?: 'localStorage' | 'sessionStorage' | 'memory';
    prefix?: string;
  };
}

Passkey Authentication

// Login with passkey
const { data, error } = await auth.passkey.login();

// Login with conditional UI (autofill)
if (await auth.passkey.isConditionalUIAvailable()) {
  const { data, error } = await auth.passkey.login({ mediation: 'conditional' });
}

// Sign up with passkey
const { data, error } = await auth.passkey.signUp({
  email: '[email protected]',
  displayName: 'John Doe',
});

// Register new passkey (user must be logged in)
const { data, error } = await auth.passkey.register();

// Check support
const isSupported = auth.passkey.isSupported();
const canAutoFill = await auth.passkey.isConditionalUIAvailable();

// Cancel conditional UI
auth.passkey.cancelConditionalUI();

Email Code Authentication

// Send verification code
const { data, error } = await auth.emailCode.send('[email protected]', {
  type: 'login', // 'login' | 'signup' | 'verification'
});

if (data) {
  console.log('Code sent! Expires in', data.expiresIn, 'seconds');
}

// Verify code and authenticate
const { data, error } = await auth.emailCode.verify('[email protected]', '123456');

// Check pending verification
const hasPending = auth.emailCode.hasPendingVerification('[email protected]');
const remainingTime = auth.emailCode.getRemainingTime('[email protected]');

// Clear pending state
auth.emailCode.clearPendingVerification('[email protected]');

Social Login

// Popup login (stays on current page)
const { data, error } = await auth.social.loginWithPopup('google');

if (error && error.error === 'popup_blocked') {
  // Fall back to redirect
  await auth.social.loginWithRedirect('google');
}

// Redirect login
await auth.social.loginWithRedirect('github', {
  redirectUri: 'https://app.example.com/callback',
});

// Handle callback (on callback page)
if (auth.social.hasCallbackParams()) {
  const { data, error } = await auth.social.handleCallback();
  if (data) {
    router.navigate('/dashboard');
  }
}

// Get supported providers
const providers = auth.social.getSupportedProviders();
// ['google', 'github', 'apple', 'microsoft', 'facebook', 'twitter']

Session Management

// Get current session
const { data } = await auth.session.get();
if (data) {
  console.log('User:', data.user);
  console.log('Session expires:', data.session.expiresAt);
}

// Validate session with server
const isValid = await auth.session.validate();

// Refresh session
const session = await auth.session.refresh();

// Check authentication status
const isAuth = await auth.session.isAuthenticated();

// Clear cache
auth.session.clearCache();

// Sign out
await auth.signOut();

// Sign out with redirect
await auth.signOut({ redirectUri: 'https://example.com' });

OAuth Namespace (when enableOAuth: true)

const auth = await createAuthrim({
  issuer: 'https://auth.example.com',
  clientId: 'your-client-id',
  enableOAuth: true,
});

// Build authorization URL manually
const { url, state, nonce } = await auth.oauth.buildAuthorizationUrl({
  redirectUri: 'https://app.example.com/callback',
  scopes: ['openid', 'profile', 'email'],
});

// Handle callback
const { data, error } = await auth.oauth.handleCallback(window.location.href);

// Silent auth (iframe)
const { data, error } = await auth.oauth.silentAuth.check({
  redirectUri: 'https://app.example.com/silent-callback',
  timeoutMs: 5000,
});

// Popup login
const { data, error } = await auth.oauth.popup.login({
  scopes: ['openid', 'profile'],
  popupFeatures: { width: 500, height: 600 },
});

Silent SSO (Cross-Domain)

Safari ITP や Chrome Third-Party Cookie Phaseout に対応した クロスドメイン SSO を実行できます。 iframe ベースの silent auth は Cookie がブロックされる環境では動作しないため、トップレベル遷移(prompt=none)を使用します。

const auth = await createAuthrim({
  issuer: 'https://auth.example.com',
  clientId: 'your-client-id',
  enableOAuth: true,
});

// 1. トップページで SSO 試行(初回のみ)
if (!(await auth.session.isAuthenticated())) {
  const ssoAttempted = sessionStorage.getItem('sso_attempted');
  if (!ssoAttempted) {
    sessionStorage.setItem('sso_attempted', 'true');

    // トップレベル遷移で IdP へ prompt=none リダイレクト
    // - IdP にセッションあり → SSO 成功 → コールバック → 元のページへ
    // - IdP にセッションなし → sso_error=login_required で戻る
    await auth.oauth.trySilentLogin({
      onLoginRequired: 'return',  // 未ログインなら元のページへ戻る
      returnTo: window.location.href,
    });
    return; // リダイレクトするのでここには到達しない
  }

  // SSO 試行済みだがログインなし → ログインボタン表示
  showLoginButton();
}

callback.html で必ず handleSilentCallback() を呼ぶ:

// callback.html または callback.ts
const auth = await createAuthrim({
  issuer: 'https://auth.example.com',
  clientId: 'your-client-id',
  enableOAuth: true,
});

// Silent Login のコールバック処理(重要!)
const result = await auth.oauth.handleSilentCallback();

if (result.status === 'error' && result.error === 'not_silent_login') {
  // Silent Login ではない → 通常の OAuth コールバック処理へ
  if (auth.social.hasCallbackParams()) {
    const { data, error } = await auth.social.handleCallback();
    // ...
  }
}

TrySilentLoginOptions:

| Option | Type | Default | Description | |--------|------|---------|-------------| | onLoginRequired | 'return' \| 'login' | 'return' | IdP にセッションがない場合の動作 | | returnTo | string | 現在のURL | 成功時・return時の戻り先URL | | scope | string | - | 追加の OAuth スコープ |

SilentLoginResult:

| Status | Description | |--------|-------------| | success | SSO 成功、セッション確立済み | | login_required | IdP にセッションなし(return 選択時) | | error | その他のエラー(error, errorDescription を参照) |

⚠️ 重要: handleSilentCallback() を呼ばないと Silent SSO が正しく動作しません。


CheckSessionIframeManager

Manages OP's check_session_iframe for session state monitoring per OIDC Session Management 1.0.

import { CheckSessionIframeManager } from '@authrim/web';

const manager = new CheckSessionIframeManager({
  checkSessionIframeUrl: 'https://auth.example.com/connect/checksession',
  clientId: 'your-client-id',
  opOrigin: 'https://auth.example.com',
  timeout: 5000, // optional, default: 5000ms
});

// Initialize iframe
await manager.initialize();

// Check session state
const result = await manager.checkSession(sessionState);

switch (result.response) {
  case 'changed':
    // Session has changed, re-authenticate
    await performSilentAuth();
    break;
  case 'unchanged':
    // Session is still valid
    break;
  case 'error':
    console.error('Check failed:', result.error);
    break;
}

// Cleanup
manager.destroy();

SessionMonitor

Automatically monitors session state with periodic polling.

import { SessionMonitor } from '@authrim/web';

const monitor = new SessionMonitor({
  checkSessionIframeUrl: 'https://auth.example.com/connect/checksession',
  clientId: 'your-client-id',
  opOrigin: 'https://auth.example.com',
  pollInterval: 2000,  // optional, default: 2000ms
  maxErrors: 3,        // optional, default: 3
});

// Subscribe to events
const unsubscribe = monitor.on((event) => {
  switch (event.type) {
    case 'session:changed':
      console.log('Session changed! Re-authenticating...');
      performSilentAuth().then((newSessionState) => {
        monitor.updateSessionState(newSessionState);
      });
      break;
    case 'session:unchanged':
      // Session is still valid (optional handling)
      break;
    case 'session:error':
      console.warn('Session check failed');
      break;
    case 'session:stopped':
      console.log('Monitor stopped:', event.reason);
      // reason: 'user_stopped' | 'too_many_errors'
      break;
  }
});

// Start monitoring
await monitor.start(initialSessionState);

// Update session state after re-auth
monitor.updateSessionState(newSessionState);

// Stop monitoring
monitor.stop();
unsubscribe();

FrontChannelLogoutHandler

Handles front-channel logout requests on the RP's logout endpoint.

import { FrontChannelLogoutHandler } from '@authrim/web';

// On your /logout page (loaded in iframe by OP)
const handler = new FrontChannelLogoutHandler({
  issuer: 'https://auth.example.com',
  sessionId: currentSessionId,  // optional, for sid validation
  requireIss: true,             // require iss parameter
  requireSid: false,            // require sid parameter
  onLogout: async (params) => {
    // Clear local session
    localStorage.removeItem('session');
    sessionStorage.clear();
    // Optionally notify your app
    window.parent?.postMessage({ type: 'logout' }, '*');
  },
});

// Check and handle logout request
if (handler.isLogoutRequest()) {
  const result = await handler.handleCurrentUrl();
  if (result.success) {
    // Show logout confirmation using safe DOM methods
    const message = document.createElement('p');
    message.textContent = 'You have been logged out.';
    document.body.appendChild(message);
  } else {
    console.error('Logout validation failed:', result.error);
  }
}

Security Considerations:

  • Always enable requireIss: true and verify the issuer
  • Use requireSid: true when session ID is available for CSRF protection
  • Front-channel logout URI must use HTTPS in production

DeviceFlowUI

UI helper for Device Authorization Grant (RFC 8628).

import { DeviceFlowUI, formatUserCode, getDeviceFlowQRCodeUrl } from '@authrim/web';
import { DeviceFlowClient } from '@authrim/core';

// Setup (typically done once)
const httpClient = new BrowserHttpClient();
const deviceClient = new DeviceFlowClient(httpClient, clientId);
const discovery = await fetchDiscovery(issuer);

// Create UI helper
const ui = new DeviceFlowUI({
  client: deviceClient,
  discovery,
  autoPolling: true,       // optional, default: true
  countdownInterval: 1000, // optional, default: 1000ms
});

// Subscribe to events
const unsubscribe = ui.on((event) => {
  switch (event.type) {
    case 'device:started':
      // Display user code and verification URI
      const userCode = formatUserCode(event.state!.userCode); // "ABCD-1234"
      const qrUrl = getDeviceFlowQRCodeUrl(event.state!);

      showUserCode(userCode);
      showQRCode(qrUrl);
      showVerificationUri(event.state!.verificationUri);
      break;

    case 'device:pending':
      // Update countdown timer
      updateCountdown(event.remainingSeconds!);
      break;

    case 'device:polling':
      // Show polling indicator
      showPollingStatus();
      break;

    case 'device:slow_down':
      // OP requested slower polling
      console.log('Slowing down polling...');
      break;

    case 'device:completed':
      // Authorization successful!
      console.log('Tokens:', event.tokens);
      hideDeviceFlowUI();
      startApp(event.tokens);
      break;

    case 'device:expired':
      showMessage('Code expired. Please try again.');
      break;

    case 'device:denied':
      showMessage('Authorization was denied.');
      break;

    case 'device:error':
      showError(event.error!.message);
      break;

    case 'device:cancelled':
      showMessage('Authorization cancelled.');
      break;
  }
});

// Start device flow
await ui.start({ scope: 'openid profile' });

// Cancel if needed (e.g., user clicks cancel button)
cancelButton.onclick = () => ui.cancel();

// Cleanup when done
unsubscribe();

Helper Functions:

// Format user code with separator
formatUserCode('ABCD1234');        // "ABCD-1234"
formatUserCode('ABCD1234', ' ');   // "ABCD 1234"
formatUserCode('ABCDEF12', '-', 3); // "ABC-DEF-12"

// Get URL for QR code (prefers verification_uri_complete)
const url = getDeviceFlowQRCodeUrl(state);
// Returns verification_uri_complete if available, otherwise verification_uri

Events

// Subscribe to auth events
const unsubscribe = auth.on('auth:login', (event) => {
  console.log('User logged in:', event.user);
  console.log('Method:', event.method); // 'passkey' | 'emailCode' | 'social'
});

auth.on('auth:logout', (event) => {
  console.log('User logged out');
  if (event.redirectUri) {
    window.location.href = event.redirectUri;
  }
});

auth.on('token:refreshed', (event) => {
  console.log('Token refreshed, session:', event.session);
});

// Unsubscribe
unsubscribe();

Available Events:

| Event | Payload | Description | |-------|---------|-------------| | auth:login | { session, user, method } | User logged in | | auth:logout | { redirectUri? } | User logged out | | token:refreshed | { session } | Token was refreshed | | session:changed | { session, user } | Session state changed | | session:expired | { reason } | Session expired | | auth:error | { error } | Authentication error |


Response Pattern

All methods return a discriminated union:

type AuthResponse<T> =
  | { data: T; error: null }
  | { data: null; error: AuthError };

interface AuthError {
  code: string;       // e.g., 'AR001001'
  error: string;      // e.g., 'invalid_credentials'
  message: string;    // Human-readable message
  retryable: boolean;
  severity: 'info' | 'warn' | 'error' | 'critical';
}

Usage:

const { data, error } = await auth.passkey.login();

if (error) {
  // Handle error
  console.log('Code:', error.code);
  console.log('Retryable:', error.retryable);

  if (error.retryable) {
    showRetryButton();
  } else {
    showErrorMessage(error.message);
  }
  return;
}

// data is guaranteed to be non-null here
console.log('User:', data.user);
console.log('Session:', data.session);

Complete Examples

SPA with Passkey Login

import { createAuthrim } from '@authrim/web';

// Initialize
const auth = await createAuthrim({
  issuer: 'https://auth.example.com',
  clientId: 'my-spa',
});

// Event listeners
auth.on('auth:login', ({ user }) => {
  updateUI(user);
});

auth.on('auth:logout', () => {
  showLoginPage();
});

// Check existing session on load
const { data } = await auth.session.get();
if (data) {
  updateUI(data.user);
} else {
  showLoginPage();
}

// Login handler
async function handleLogin() {
  const { data, error } = await auth.passkey.login();
  if (error) {
    showError(error.message);
  }
}

// Logout handler
async function handleLogout() {
  await auth.signOut();
}

OAuth with Session Management

import { createAuthrim, SessionMonitor } from '@authrim/web';

// Initialize with OAuth
const auth = await createAuthrim({
  issuer: 'https://auth.example.com',
  clientId: 'my-app',
  enableOAuth: true,
});

// Login with popup
const { data, error } = await auth.oauth.popup.login({
  scopes: ['openid', 'profile', 'email'],
});

if (error) {
  console.error('Login failed:', error.message);
  return;
}

// Start session monitoring
const monitor = new SessionMonitor({
  checkSessionIframeUrl: 'https://auth.example.com/connect/checksession',
  clientId: 'my-app',
  opOrigin: 'https://auth.example.com',
});

monitor.on((event) => {
  if (event.type === 'session:changed') {
    // Attempt silent re-auth
    auth.oauth.silentAuth.check({
      redirectUri: 'https://app.example.com/silent-callback',
    }).then(({ data, error }) => {
      if (data) {
        monitor.updateSessionState(data.sessionState);
      } else {
        // Silent auth failed, redirect to login
        auth.signOut();
      }
    });
  }
});

await monitor.start(data.sessionState);

CLI/TV App with Device Flow

import { DeviceFlowUI, formatUserCode, getDeviceFlowQRCodeUrl } from '@authrim/web';

// Initialize Device Flow UI
const ui = new DeviceFlowUI({
  client: deviceFlowClient,
  discovery,
});

// Handle events
ui.on((event) => {
  switch (event.type) {
    case 'device:started':
      console.log('\n=== Authorization Required ===');
      console.log(`Visit: ${event.state!.verificationUri}`);
      console.log(`Code:  ${formatUserCode(event.state!.userCode)}`);
      console.log('==============================\n');
      break;
    case 'device:pending':
      process.stdout.write(`\rWaiting... ${event.remainingSeconds}s remaining`);
      break;
    case 'device:completed':
      console.log('\n\nAuthorization successful!');
      saveTokens(event.tokens);
      break;
    case 'device:expired':
      console.log('\n\nCode expired. Please restart.');
      process.exit(1);
      break;
  }
});

// Start and wait
await ui.start({ scope: 'openid profile' });

Storage Security

| Type | Persistence | XSS Risk | Recommendation | |------|-------------|----------|----------------| | memory | Tab only | Lowest | SPA recommended | | sessionStorage | Tab/reload | Medium | Default | | localStorage | Permanent | Highest | Explicit opt-in only |


Browser Support

| Browser | Version | WebAuthn | |---------|---------|----------| | Chrome | 67+ | 67+ | | Firefox | 60+ | 60+ | | Safari | 13+ | 14+ | | Edge | 79+ | 79+ |

WebAuthn Requirements:

  • HTTPS required (except localhost)
  • User gesture required for credential creation

TypeScript

Full TypeScript support with type inference:

import type {
  // Main types
  Authrim,
  AuthrimConfig,
  AuthResponse,
  AuthError,
  AuthSessionData,
  User,
  Session,

  // Namespaces
  PasskeyNamespace,
  EmailCodeNamespace,
  SocialNamespace,
  SessionNamespace,
  OAuthNamespace,

  // Events
  AuthEventName,
  AuthEventHandler,
  AuthEventPayloads,

  // Session Management
  CheckSessionIframeManagerOptions,
  CheckSessionResult,
  SessionMonitorOptions,
  SessionMonitorEvent,
  SessionMonitorEventType,
  FrontChannelLogoutHandlerOptions,
  FrontChannelLogoutHandleResult,

  // Device Flow
  DeviceFlowUIOptions,
  DeviceFlowUIEvent,
  DeviceFlowUIEventType,
} from '@authrim/web';

Development

# Install dependencies
pnpm install

# Run tests
pnpm test

# Type check
pnpm typecheck

# Build
pnpm build

# Watch mode
pnpm dev

# Format code
pnpm format

# Lint
pnpm lint

License

Apache-2.0


Related Packages

| Package | Description | Status | |---------|-------------|--------| | @authrim/core | Platform-agnostic core library | ✅ Available | | @authrim/react | React hooks and components | 🚧 Planned | | @authrim/sveltekit | SvelteKit integration | ✅ Available | | @authrim/vue | Vue.js integration | 🚧 Planned |


Links