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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@micahgoodman/auth

v2.0.0

Published

Complete authentication package for React with Supabase and Keycloak support. Includes email/password auth and embedded SDK mode for federated identity.

Readme

@micahgoodman/auth

Complete authentication package for React with Supabase and Keycloak support. Includes email/password auth, OAuth with Keycloak, and embedded SDK mode for federated identity.

Features

🔐 Multiple Auth Strategies - Email/password, Keycloak OAuth, embedded SDK mode
🔗 Single Sign-On (SSO) - Keycloak integration for federated identity
📦 Embedded SDK Mode - Use as library with remote backend authentication
🎨 Ready-to-Use UI - Pre-built modals and auth button
⚛️ React Context - Easy state management across your app
📱 TypeScript - Full type safety
🔌 Supabase Integration - Built for Supabase Auth with Keycloak provider

Installation

Basic Installation (Email/Password Auth)

npm install @micahgoodman/auth @supabase/supabase-js

With Keycloak Support

npm install @micahgoodman/auth @supabase/supabase-js keycloak-js

Three Authentication Modes

This package supports three authentication modes:

  1. Email/Password Auth (default) - Traditional auth with Supabase
  2. Keycloak Auth - For standalone apps using Keycloak OAuth
  3. Embedded SDK Mode - For components embedded in parent apps with SSO

Mode 1: Email/Password Auth (Default)

Use this for: Traditional email/password authentication

import { createSupabaseClient, AuthProvider, AuthButton } from '@micahgoodman/auth';

// 1. Create Supabase client
const supabase = createSupabaseClient({
  supabaseUrl: import.meta.env.VITE_SUPABASE_URL,
  supabaseKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
});

// 2. Wrap your app with AuthProvider
function App() {
  return (
    <AuthProvider supabaseClient={supabase}>
      <YourApp />
    </AuthProvider>
  );
}

// 3. Use AuthButton or useAuth hook
function Header() {
  return (
    <header>
      <h1>My App</h1>
      <AuthButton />
    </header>
  );
}

Mode 2: Keycloak Auth (Standalone)

Use this for: Standalone apps using Keycloak for OAuth authentication

Setup

import Keycloak from 'keycloak-js';
import { 
  createSupabaseClient, 
  KeycloakAuthProvider, 
  useKeycloakAuth 
} from '@micahgoodman/auth';

// 1. Initialize Keycloak
const keycloak = new Keycloak({
  url: 'https://keycloak.example.com',
  realm: 'my-realm',
  clientId: 'my-app'
});

// 2. Create Supabase client
const supabase = createSupabaseClient({
  supabaseUrl: import.meta.env.VITE_SUPABASE_URL,
  supabaseKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
});

// 3. Wrap app with KeycloakAuthProvider
function App() {
  return (
    <KeycloakAuthProvider
      supabaseClient={supabase}
      keycloakInstance={keycloak}
      onLoad="login-required"
    >
      <YourApp />
    </KeycloakAuthProvider>
  );
}

// 4. Use in components
function MyComponent() {
  const { user, signOut, keycloakAuthenticated } = useKeycloakAuth();
  
  return (
    <div>
      <p>Welcome, {user?.email}</p>
      <button onClick={signOut}>Sign Out</button>
    </div>
  );
}

Supabase Configuration

Configure Keycloak as auth provider in Supabase dashboard:

import { getSupabaseKeycloakConfig } from '@micahgoodman/auth';

const config = getSupabaseKeycloakConfig({
  url: 'https://keycloak.example.com',
  realm: 'my-realm',
  clientId: 'my-app'
});

// Use in Supabase Dashboard:
// Authentication > Providers > Keycloak
// Keycloak URL: config.keycloakUrl
// Client ID: config.clientId

Mode 3: Embedded SDK Mode (Recommended for Embedded Apps)

Use this for: Embedding components in a parent app with existing Keycloak authentication

Key Benefits:

  • ✅ User logs in ONCE in parent app
  • ✅ No additional login UI for embedded components
  • ✅ Silent token exchange (user sees nothing)
  • ✅ Automatic token refresh
  • ✅ Works across multiple embedded modules

Parent App Setup

import Keycloak from 'keycloak-js';
import { RemoteAuthProvider } from '@micahgoodman/auth';
import { NotesComponents } from '@company/notes';

// 1. Initialize Keycloak (parent app)
const keycloak = new Keycloak({
  url: 'https://keycloak.example.com',
  realm: 'shared-realm',
  clientId: 'parent-app'
});

function ParentApp() {
  const [token, setToken] = React.useState<string>();

  React.useEffect(() => {
    // User logs in ONCE here
    keycloak.init({ onLoad: 'login-required' }).then((authenticated) => {
      if (authenticated) {
        setToken(keycloak.token);
      }
    });

    // Handle token refresh
    keycloak.onTokenExpired = () => {
      keycloak.updateToken(30).then(() => {
        setToken(keycloak.token);
      });
    };
  }, []);

  if (!token) return <div>Loading...</div>;

  return (
    <div className="app">
      <h1>Productivity Suite</h1>
      
      {/* Embedded notes - no additional login! */}
      <RemoteAuthProvider
        supabaseClient={notesSupabase}
        keycloakToken={token}
        onTokenExpired={async () => {
          await keycloak.updateToken(30);
          return keycloak.token!;
        }}
      >
        <NotesComponents />
      </RemoteAuthProvider>
    </div>
  );
}

Embedded Module Setup

In your embedded npm package:

import { createSupabaseClient, configureNotesApi } from '@company/notes';

// Point to the remote Supabase backend
const notesSupabase = createSupabaseClient({
  supabaseUrl: 'https://notes-backend.supabase.co',
  supabaseKey: 'notes-anon-key'
});

// Components automatically authenticated via RemoteAuthProvider
export function NotesComponents() {
  const { user } = useRemoteAuth();
  // user is authenticated - no login needed!
  return <div>Notes for {user?.email}</div>;
}

How It Works

  1. User logs into parent app with Keycloak (sees login UI ONCE)
  2. Parent app gets Keycloak token
  3. Parent passes token to RemoteAuthProvider
  4. Provider silently exchanges token for Supabase session (backend-to-backend)
  5. Embedded components are now authenticated - user never sees another login!

This is federated identity - one login, multiple services.


Unified Auth Hook

Use useUnifiedAuth() to write components that work with ANY auth provider:

import { useUnifiedAuth } from '@micahgoodman/auth';

function MyComponent() {
  const { user, signOut, loading } = useUnifiedAuth();
  // Works with AuthProvider, KeycloakAuthProvider, or RemoteAuthProvider!
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <div>
      {user && (
        <>
          <p>Welcome, {user.email}</p>
          <button onClick={signOut}>Sign Out</button>
        </>
      )}
    </div>
  );
}

Components

<AuthButton />

All-in-one auth UI - shows sign-in button or profile button based on auth state.

<AuthButton />

<AuthModal />

Sign in/sign up modal.

const [showAuth, setShowAuth] = useState(false);
<AuthModal onClose={() => setShowAuth(false)} />

<ProfileModal />

User profile management modal.

const [showProfile, setShowProfile] = useState(false);
<ProfileModal onClose={() => setShowProfile(false)} />

API Reference

Hooks

useAuth()

For email/password authentication (works with AuthProvider).

import { useAuth } from '@micahgoodman/auth';

const { 
  user, 
  session, 
  loading, 
  signIn, 
  signOut, 
  signUp, 
  resetPassword, 
  updateProfile 
} = useAuth();

useKeycloakAuth()

For Keycloak authentication (works with KeycloakAuthProvider).

import { useKeycloakAuth } from '@micahgoodman/auth';

const { 
  user, 
  session, 
  loading, 
  keycloakAuthenticated,
  keycloak, // Access to Keycloak instance
  signIn,
  signOut,
  error 
} = useKeycloakAuth();

useRemoteAuth()

For embedded SDK mode (works with RemoteAuthProvider).

import { useRemoteAuth } from '@micahgoodman/auth';

const { 
  user, 
  session, 
  loading, 
  error, 
  signOut 
} = useRemoteAuth();

useUnifiedAuth()

Works with ANY auth provider - writes components once, works everywhere.

import { useUnifiedAuth } from '@micahgoodman/auth';

const { user, session, loading, signOut } = useUnifiedAuth();
// Automatically detects and uses the correct provider

Configuration Utilities

getSupabaseKeycloakConfig()

Generate Supabase Keycloak provider configuration.

import { getSupabaseKeycloakConfig } from '@micahgoodman/auth';

const config = getSupabaseKeycloakConfig({
  url: 'https://keycloak.example.com',
  realm: 'my-realm',
  clientId: 'my-app'
});
// Returns: { keycloakUrl, clientId }

getKeycloakConfigFromEnv()

Load Keycloak config from environment variables.

import { getKeycloakConfigFromEnv } from '@micahgoodman/auth';

const config = getKeycloakConfigFromEnv();
// Reads VITE_KEYCLOAK_URL, VITE_KEYCLOAK_REALM, VITE_KEYCLOAK_CLIENT_ID

KEYCLOAK_INIT_OPTIONS

Pre-configured Keycloak init options.

import { KEYCLOAK_INIT_OPTIONS } from '@micahgoodman/auth';

keycloak.init(KEYCLOAK_INIT_OPTIONS.LOGIN_REQUIRED);
// or
keycloak.init(KEYCLOAK_INIT_OPTIONS.CHECK_SSO);

Environment Setup

For Email/Password Auth

VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key

For Keycloak Auth

VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
VITE_KEYCLOAK_URL=https://keycloak.example.com
VITE_KEYCLOAK_REALM=my-realm
VITE_KEYCLOAK_CLIENT_ID=my-app

For Embedded SDK Mode (Parent App)

# Parent app's own Supabase (optional)
VITE_SUPABASE_URL=https://parent-project.supabase.co
VITE_SUPABASE_ANON_KEY=parent-anon-key

# Keycloak (shared across all apps)
VITE_KEYCLOAK_URL=https://keycloak.example.com
VITE_KEYCLOAK_REALM=shared-realm
VITE_KEYCLOAK_CLIENT_ID=parent-app

# Each embedded module's Supabase backend
VITE_NOTES_SUPABASE_URL=https://notes-backend.supabase.co
VITE_NOTES_SUPABASE_ANON_KEY=notes-anon-key

Supabase Dashboard Configuration

Email/Password Auth

  • Enable email auth in Supabase dashboard
  • Configure email templates
  • Set up RLS policies for your tables

Keycloak Provider

  1. Go to Authentication > Providers > Keycloak

  2. Enable Keycloak provider

  3. Configure:

    • Keycloak URL: https://your-keycloak.com/realms/your-realm
    • Client ID: Your app's Keycloak client ID
    • Client Secret: From Keycloak client settings
  4. Set callback URL in Keycloak:

    • Valid Redirect URIs: https://your-project.supabase.co/auth/v1/callback
    • Web Origins: https://your-app.com
  5. Enable Direct Access Grants in Keycloak client settings

Styling

The package uses minimal inline styles for maximum flexibility:

  • Override with CSS classes
  • Provide your own modal components
  • Use hooks directly for complete UI control
// Custom UI with hooks
import { useUnifiedAuth } from '@micahgoodman/auth';

function CustomAuthUI() {
  const { user, signOut } = useUnifiedAuth();
  return (
    <YourCustomUI user={user} onSignOut={signOut} />
  );
}

TypeScript

import type {
  SupabaseConfig,
  AuthProviderProps,
} from '@micahgoodman/auth';

import type {
  User,
  Session,
  AuthError,
} from '@supabase/supabase-js';

Advanced Usage

Multi-Module Architecture

For apps with multiple embedded modules:

import Keycloak from 'keycloak-js';
import { RemoteAuthProvider } from '@micahgoodman/auth';

const keycloak = new Keycloak(/* config */);

function App() {
  const [token, setToken] = React.useState<string>();
  
  // ... Keycloak initialization ...
  
  return (
    <div>
      {/* Notes Module */}
      <RemoteAuthProvider 
        supabaseClient={notesSupabase}
        keycloakToken={token}
        onTokenExpired={refreshToken}
      >
        <NotesSection />
      </RemoteAuthProvider>
      
      {/* Tasks Module */}
      <RemoteAuthProvider 
        supabaseClient={tasksSupabase}
        keycloakToken={token}
        onTokenExpired={refreshToken}
      >
        <TasksSection />
      </RemoteAuthProvider>
      
      {/* Each module has its own Supabase backend */}
      {/* All share the same Keycloak realm */}
      {/* User logs in ONCE */}
    </div>
  );
}

Protected Routes

import { useUnifiedAuth, isAuthenticated } from '@micahgoodman/auth';

function ProtectedRoute({ children }) {
  const auth = useUnifiedAuth();

  if (auth.loading) return <Loading />;
  if (!isAuthenticated(auth)) return <Navigate to="/login" />;

  return children;
}

Troubleshooting

"Module not found: keycloak-js"

Solution: Keycloak is an optional dependency. Install it if using Keycloak features:

npm install keycloak-js

"CORS error" in Embedded SDK Mode

Solution: Configure CORS in Supabase Edge Functions:

const corsHeaders = {
  'Access-Control-Allow-Origin': 'https://parent-app.com',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

"Unauthorized" errors with Keycloak

Checklist:

  • ✅ Keycloak provider configured in Supabase dashboard
  • ✅ Client ID matches in Keycloak and Supabase
  • ✅ All apps use the same Keycloak realm
  • ✅ Redirect URIs configured in Keycloak
  • ✅ Direct Access Grants enabled

Token not refreshing in Embedded SDK Mode

Solution: Implement onTokenExpired callback:

<RemoteAuthProvider
  keycloakToken={token}
  onTokenExpired={async () => {
    await keycloak.updateToken(30);
    return keycloak.token!;
  }}
>

User sees login twice

Issue: You're using the wrong provider for embedded mode.

Solution: Use RemoteAuthProvider (not KeycloakAuthProvider) in embedded components:

// ❌ Wrong - shows login UI again
<KeycloakAuthProvider keycloakInstance={keycloak}>

// ✅ Correct - silent token exchange
<RemoteAuthProvider keycloakToken={token}>

Migration from v1.x

No Breaking Changes!

Existing email/password auth code works unchanged:

// v1.x code - still works in v2.x
import { AuthProvider, useAuth } from '@micahgoodman/auth';

New Features Available

  • ✅ Keycloak authentication
  • ✅ Embedded SDK mode
  • ✅ Unified auth hook
  • ✅ Configuration utilities

Adopt these features incrementally at your own pace.

Architecture Decisions

Why Three Providers?

Each provider serves a specific use case:

  1. AuthProvider - Traditional apps with email/password auth
  2. KeycloakAuthProvider - Standalone apps using OAuth/SSO
  3. RemoteAuthProvider - Embedded components (library mode with remote backend)

Why Not a Single Provider?

Separate providers:

  • ✅ Keep bundle size small (only import what you need)
  • ✅ Make code intent clear
  • ✅ Allow different initialization patterns
  • ✅ Avoid prop confusion

NPM Package Scope

This package handles:

  • Token exchange (signInWithIdToken)
  • Session management
  • Token refresh
  • Auth state management
  • UI components

Your app handles:

  • Keycloak instance initialization
  • Environment configuration
  • App-specific routing
  • Backend setup

This separation keeps the package focused and reusable.

Examples

See the /examples directory (coming soon) for:

  • Email/password app
  • Keycloak standalone app
  • Parent app with embedded modules
  • Multi-module architecture

Contributing

Contributions welcome! Please open an issue first to discuss changes.

Support

For issues or questions:

License

MIT