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

@nightmar3/uauth-react

v1.0.6

Published

React hooks and components for Universal Auth SDK

Readme

@nightmar3/uauth-react

React hooks and components for Universal Auth SDK. Works with any backend implementing the Universal Auth contract.

[!IMPORTANT] Backend Required: This SDK requires a backend API. See Backend Requirements for the complete API contract, or use our Backend Implementation Guide for step-by-step instructions.

Setup Guide

Step 1: Install Packages

npm install @nightmar3/uauth-core @nightmar3/uauth-react

Step 2: Create Auth Instance

// lib/auth.ts
import { createAuth } from '@nightmar3/uauth-core'

export const auth = createAuth({
  baseURL: 'https://api.yourapp.com/auth',
  storage: localStorage,
})

Step 3: Wrap Your App with AuthProvider

// App.tsx
import { AuthProvider } from '@nightmar3/uauth-react'
import { auth } from './lib/auth'

function App() {
  return (
    <AuthProvider auth={auth}>
      <YourApp />
    </AuthProvider>
  )
}

Step 4: Create Login Form

import { useState } from 'react'
import { useAuth } from '@nightmar3/uauth-react'

function LoginForm() {
  const { signIn, isLoading, error } = useAuth()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    const result = await signIn('password', { email, password })

    if (result.ok) {
      // Redirect or show success
      console.log('Logged in:', result.data?.user)
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Signing in...' : 'Sign In'}
      </button>
      {error && <div className="error">{error.message}</div>}
    </form>
  )
}

Step 5: Create Protected Content

import { RequireAuth, useAuth } from '@nightmar3/uauth-react'

function Dashboard() {
  const { user, signOut } = useAuth()

  return (
    <div>
      <h1>Welcome {user?.name}</h1>
      <button onClick={signOut}>Sign Out</button>
    </div>
  )
}

function App() {
  return (
    <RequireAuth fallback={<LoginForm />}>
      <Dashboard />
    </RequireAuth>
  )
}

That's it! Your React app now has full authentication support.


Use Cases

Email/Password Authentication

const { signIn, signUp, isLoading, error } = useAuth()

// Sign in
const result = await signIn('password', { email, password })

// Sign up
const result = await signUp({ email, password, name })

OAuth Authentication (Optional)

OAuth is completely optional. If you don't need OAuth, skip this section - no extra network requests will be made.

To add OAuth support:

1. Add the OAuth2 plugin to AuthProvider:

import { createAuth, createOAuth2Plugin } from '@nightmar3/uauth-core'
import { AuthProvider } from '@nightmar3/uauth-react'

const auth = createAuth({
  baseURL: 'https://api.yourapp.com/auth',
  storage: localStorage,
})

// Only include if you want OAuth
const plugins = [createOAuth2Plugin()]

function App() {
  return (
    <AuthProvider auth={auth} plugins={plugins}>
      <YourApp />
    </AuthProvider>
  )
}

2. Use the useOAuth hook:

import { useOAuth } from '@nightmar3/uauth-react'

function OAuthButtons() {
  const { providers, isLoading, signInWithOAuth } = useOAuth()

  // No OAuth configured - render nothing
  if (providers.length === 0) return null

  if (isLoading) return <div>Loading...</div>

  return (
    <div>
      {providers.map((provider) => (
        <button
          key={provider.name}
          onClick={() => signInWithOAuth(provider.name)}
        >
          Continue with {provider.displayName || provider.name}
        </button>
      ))}
    </div>
  )
}

3. Complete login page with OAuth:

import { useState } from 'react'
import { useAuth, useOAuth } from '@nightmar3/uauth-react'

function LoginPage() {
  const { signIn, isLoading, error } = useAuth()
  const { providers, signInWithOAuth, isLoading: oauthLoading } = useOAuth()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    await signIn('password', { email, password })
  }

  return (
    <div>
      {/* OAuth Buttons (only shows if providers available) */}
      {providers.length > 0 && (
        <>
          <div className="oauth-buttons">
            {providers.map((p) => (
              <button
                key={p.name}
                onClick={() => signInWithOAuth(p.name)}
                disabled={oauthLoading}
              >
                Continue with {p.displayName}
              </button>
            ))}
          </div>
          <div className="divider">or</div>
        </>
      )}

      {/* Email/Password Form */}
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Email"
        />
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="Password"
        />
        <button type="submit" disabled={isLoading}>
          {isLoading ? 'Signing in...' : 'Sign In'}
        </button>
      </form>

      {error && <div className="error">{error.message}</div>}
    </div>
  )
}

Protected Routes (React Router)

import { Navigate } from 'react-router-dom'
import { RequireAuth } from '@nightmar3/uauth-react'

function ProtectedRoute({ children }) {
  return (
    <RequireAuth fallback={<Navigate to="/login" />}>
      {children}
    </RequireAuth>
  )
}

// Usage in routes
<Route path="/dashboard" element={
  <ProtectedRoute>
    <Dashboard />
  </ProtectedRoute>
} />

Role-Based Access Control

import { AuthGuard } from '@nightmar3/uauth-react'

function AdminRoute({ children }) {
  return (
    <AuthGuard
      check={(user) => user.role === 'admin'}
      fallback={<Navigate to="/" />}
    >
      {children}
    </AuthGuard>
  )
}

Custom User Type

interface MyUser {
  id: string
  email: string
  name: string
  role: 'admin' | 'user'
}

// Type the hook
const { user } = useAuth<MyUser>()

// user is typed as MyUser | null
if (user) {
  console.log(user.role) // 'admin' | 'user'
}

// Type the AuthGuard
<AuthGuard<MyUser> check={(user) => user.role === 'admin'}>
  <AdminPanel />
</AuthGuard>

Backend Requirements

The SDK expects a backend API that implements the following endpoints. You can use our FastAPI reference implementation or build your own.

Endpoints

| Method | Path | Description | Request Body | Response Data | |--------|------|-------------|--------------|---------------| | POST | /sign-in/password | Sign in with email/password | { email, password } | { user, tokens } | | POST | /sign-in/oauth2 | Exchange OAuth code for tokens | { provider, code, redirect_uri } | { user, tokens } | | POST | /sign-up | Create new account | { email, password, name? } | { user, tokens } | | DELETE| /session | Sign out (revoke tokens) | - | { ok: boolean } | | GET | /session | Get current user session | - | { user } | | POST | /token/refresh | Refresh access token | { refresh_token } | { tokens } | | GET | /providers | List available OAuth providers | - | { providers } |

Response Format

All API responses must follow this envelope structure:

interface ApiResponse<T> {
  ok: boolean
  data: T | null
  error: {
    code: string
    message: string
    details?: any
  } | null
}

Token Structure

The backend must return tokens in this format:

interface AuthTokens {
  access_token: string
  refresh_token: string
  expires_in: number // seconds
}

OAuth Provider Response (Optional)

If implementing OAuth support, the /providers endpoint must return:

interface OAuth2Provider {
  name: string
  displayName: string
  clientId: string
  authorizationUrl: string
  scope?: string
}

Example:

{
  "ok": true,
  "data": {
    "providers": [
      {
        "name": "google",
        "displayName": "Google",
        "clientId": "your-google-client-id",
        "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth",
        "scope": "openid email profile"
      }
    ]
  },
  "error": null
}

API Reference

AuthProvider

Provides authentication context to your app.

<AuthProvider
  auth={authInstance}
  plugins={[]}              // Optional: plugins like OAuth2
  loadOnMount={true}        // Optional: load session on mount (default: true)
  autoRefresh={true}        // Optional: auto-refresh tokens before expiry (default: true)
  refreshBeforeExpiry={60}  // Optional: seconds before expiry to refresh (default: 60)
>
  {children}
</AuthProvider>

| Prop | Type | Default | Description | |------|------|---------|-------------| | auth | UniversalAuth | Required | Auth instance from createAuth() | | plugins | Plugin[] | [] | Plugins to install (e.g., OAuth2) | | loadOnMount | boolean | true | Load session on mount | | autoRefresh | boolean | true | Auto-refresh tokens before expiry | | refreshBeforeExpiry | number | 60 | Seconds before expiry to refresh |

useAuth()

Main hook for accessing auth state and methods.

const {
  user,             // Current user or null
  isLoading,        // Loading state
  isAuthenticated,  // True if user is logged in
  error,            // Last error or null
  signIn,           // Sign in function
  signUp,           // Sign up function
  signOut,          // Sign out function
  refresh,          // Refresh tokens
  refetch,          // Reload session
  setUser,          // Manually set user
  getPlugin,        // Get installed plugin by name
  pluginsReady,     // Whether plugins are installed
  auth,             // The auth instance
} = useAuth()

useOAuth() (requires OAuth2 plugin)

Hook for OAuth authentication. Only works when createOAuth2Plugin() is passed to AuthProvider.

const {
  providers,         // Available OAuth providers
  isLoading,         // Loading state
  signInWithOAuth,   // Sign in with OAuth provider
} = useOAuth()

| Property | Type | Description | |----------|------|-------------| | providers | OAuth2Provider[] | Available providers from backend | | isLoading | boolean | Whether providers are loading | | signInWithOAuth | (provider: string) => Promise<void> | Trigger OAuth flow |

RequireAuth

Only renders children if user is authenticated.

<RequireAuth
  fallback={<LoginPage />}
  loadingFallback={<Spinner />}
>
  <ProtectedContent />
</RequireAuth>

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | Required | Content to show when authenticated | | fallback | ReactNode | null | Content to show when not authenticated | | loadingFallback | ReactNode | null | Content to show while loading |

GuestOnly

Only renders children if user is NOT authenticated.

<GuestOnly fallback={<Navigate to="/dashboard" />}>
  <LoginPage />
</GuestOnly>

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | Required | Content to show when not authenticated | | fallback | ReactNode | null | Content to show when authenticated | | loadingFallback | ReactNode | null | Content to show while loading |

AuthGuard

Advanced guard with custom check function.

<AuthGuard
  check={(user) => user.role === 'admin'}
  fallback={<AccessDenied />}
>
  <AdminPanel />
</AuthGuard>

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode \| (user) => ReactNode | Required | Content or render function | | check | (user) => boolean | - | Custom validation function | | fallback | ReactNode | null | Content when check fails | | loadingFallback | ReactNode | null | Content while loading |


Patterns

Auto Token Refresh

By default, tokens are automatically refreshed before they expire. This happens silently in the background - no user interaction required.

// Default: auto-refresh enabled, refreshes 60 seconds before expiry
<AuthProvider auth={auth}>
  <App />
</AuthProvider>

// Customize when to refresh (e.g., 5 minutes before expiry)
<AuthProvider auth={auth} refreshBeforeExpiry={300}>
  <App />
</AuthProvider>

// Disable auto-refresh (not recommended)
<AuthProvider auth={auth} autoRefresh={false}>
  <App />
</AuthProvider>

How it works:

  1. When user signs in or session loads, a timer is set based on token expiry
  2. Token is refreshed automatically before it expires
  3. On sign out, the timer is cleared
  4. If refresh fails, the next API request will trigger a 401 → automatic refresh retry

Loading States

function App() {
  const { isLoading } = useAuth()

  if (isLoading) {
    return <FullPageSpinner />
  }

  return <YourApp />
}

Error Handling

function LoginForm() {
  const { signIn, error } = useAuth()
  const [localError, setLocalError] = useState(null)

  const handleSubmit = async (data) => {
    setLocalError(null)
    const result = await signIn('password', data)

    if (!result.ok) {
      setLocalError(result.error?.message || 'Login failed')
    }
  }

  return (
    <>
      {localError && <Alert>{localError}</Alert>}
      {error && <Alert>{error.message}</Alert>}
      <form onSubmit={handleSubmit}>...</form>
    </>
  )
}

Manual Session Refresh

function RefreshButton() {
  const { refresh, refetch } = useAuth()

  const handleRefresh = async () => {
    await refresh()  // Refresh tokens
    await refetch()  // Reload user data
  }

  return <button onClick={handleRefresh}>Refresh Session</button>
}

Conditionally Enable OAuth

// Only enable OAuth in certain environments
const plugins = process.env.REACT_APP_ENABLE_OAUTH === 'true'
  ? [createOAuth2Plugin()]
  : []

function App() {
  return (
    <AuthProvider auth={auth} plugins={plugins}>
      <YourApp />
    </AuthProvider>
  )
}

Legacy OAuth2 API

The previous OAuth2Provider and useOAuth2 APIs are still available for backwards compatibility, but we recommend using the new plugin-based approach with useOAuth().

// Legacy (still works)
import { OAuth2Provider, useOAuth2 } from '@nightmar3/uauth-react'

// New approach (recommended)
import { AuthProvider, useOAuth } from '@nightmar3/uauth-react'
import { createOAuth2Plugin } from '@nightmar3/uauth-core'

License

MIT