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

@vuer-ai/vuer-auth-client

v1.0.0

Published

A lightweight OIDC/OAuth 2.0 client with PKCE support for React applications

Readme

Vuer Auth Client

A lightweight OIDC/OAuth2 authentication client with PKCE support, powered by Zustand for React applications.

Features

  • OIDC/OAuth2 with PKCE: Secure authorization code flow with PKCE
  • Dual Auth Modes: Popup or redirect-based authentication
  • Zustand Powered: Efficient state management with Zustand
  • Type-Safe: Full TypeScript support with type inference
  • React Hooks: Clean, intuitive hooks API (useUserinfo, useAuth)
  • Framework Agnostic Core: Core client can be used without React
  • Token Management: Automatic token storage and refresh support
  • Lightweight: Minimal dependencies, tree-shakeable

Installation

pnpm add @vuer-ai/vuer-auth-client zustand react
# or
npm install @vuer-ai/vuer-auth-client zustand react

Quick Start

1. Create Auth Client

import { createAuthClient } from '@vuer-ai/vuer-auth-client';

const authClient = createAuthClient({
  baseURL: 'https://your-auth-server.com',
  clientId: 'your-client-id',
  redirectUri: '/auth/callback',
});

export { authClient };

2. Use in Components

import { authClient } from './auth';
import { AuthMode } from '@vuer-ai/vuer-auth-client';

function App() {
  const { userinfo, isPending, error } = authClient.useUserinfo();

  if (isPending) return <div>Loading...</div>;
  if (!userinfo) return <LoginForm />;

  return (
    <div>
      <h1>Welcome, {userinfo.name}!</h1>
      <button onClick={() => authClient.signOut()}>Sign Out</button>
    </div>
  );
}

function LoginForm() {
  const handleLogin = async () => {
    // Popup mode - returns token directly
    const token = await authClient.signIn({
      authMode: AuthMode.Popup,
      callbackUrl: '/dashboard',
    });

    if (token) {
      console.log('Logged in successfully!');
    }
  };

  const handleRedirectLogin = () => {
    // Redirect mode - redirects to auth provider
    authClient.signIn({
      authMode: AuthMode.Redirect,
      callbackUrl: '/dashboard',
    });
  };

  return (
    <div>
      <button onClick={handleLogin}>Sign In (Popup)</button>
      <button onClick={handleRedirectLogin}>Sign In (Redirect)</button>
    </div>
  );
}

3. Handle Callback (Redirect Mode Only)

// pages/auth/callback/page.tsx
import { useEffect } from 'react';
import { authClient } from '@/auth';

export default function AuthCallback() {
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    const state = params.get('state');

    if (code && state) {
      authClient.getToken(code, state);
      // Will automatically redirect to callbackUrl after success
    }
  }, []);

  return <div>Completing sign in...</div>;
}

API Reference

Configuration

interface AuthClientConfig {
  /** Auth server base URL (e.g., 'https://auth.example.com') */
  baseURL?: string;
  /** OAuth2 client ID */
  clientId?: string;
  /** OAuth2 redirect URI (e.g., '/auth/callback') */
  redirectUri?: string;
  /** Additional fetch options */
  fetchOptions?: RequestInit;
}

Core Methods

signIn(auth)

Initiates the authentication flow.

// Popup mode - returns token directly
const token = await authClient.signIn({
  authMode: AuthMode.Popup,
  callbackUrl: '/dashboard',
});

// Redirect mode - redirects user to auth provider
authClient.signIn({
  authMode: AuthMode.Redirect,
  callbackUrl: '/dashboard',
});

Parameters:

  • authMode: AuthMode.Popup or AuthMode.Redirect
  • callbackUrl: URL to redirect to after successful authentication

Returns:

  • Popup mode: Promise<Token | null>
  • Redirect mode: null (performs redirect)

signOut(redirectUri?)

Signs out the user and clears tokens.

authClient.signOut(); // Redirects to '/'
authClient.signOut('/login'); // Redirects to '/login'

getToken(code, state)

Exchanges authorization code for tokens (used in redirect mode callback).

await authClient.getToken(code, state);

getUserinfo()

Fetches user information from the OIDC provider.

const user = await authClient.getUserinfo();
console.log(user.name, user.email);

refreshToken()

Refreshes the access token using the refresh token.

const newToken = await authClient.refreshToken();

token()

Gets the current token from storage.

const currentToken = authClient.token();
console.log(currentToken?.access_token);

$fetch(path, init?)

Low-level fetch wrapper with error handling.

const { data, error } = await authClient.$fetch('/api/custom', {
  method: 'POST',
  body: JSON.stringify({ foo: 'bar' }),
});

React Hooks

useUserinfo()

Hook for accessing user information with reactive state.

const { userinfo, isPending, isRefetching, error, refetch } = authClient.useUserinfo();

Returns:

{
  userinfo: User | null;
  isPending: boolean;      // Initial loading state
  isRefetching: boolean;   // Refetching state
  error: Error | null;
  refetch: () => Promise<void>;
}

useAuth()

Convenience hook for authentication state and actions.

const { user, isAuthenticated, isPending, signIn, signOut, token, refetch } = authClient.useAuth();

Returns:

{
  user: User | null;
  isAuthenticated: boolean;
  isPending: boolean;
  signIn: (auth) => Promise<Token | null>;
  signOut: (redirectUri?: string) => void;
  token: () => Token | null;
  refetch: () => Promise<void>;
}

Types

import type {
  AuthClientConfig,
  Session,
  User,
  Token,
  AuthMode,
  FetchError,
} from '@vuer-ai/vuer-auth-client';

interface User {
  sub: string;
  name?: string;
  email?: string;
  picture?: string;
  given_name?: string;
  family_name?: string;
  email_verified?: string;
}

interface Token {
  access_token: string;
  refresh_token: string;
  id_token: string;
  token_type: string;
  expires_in: number;
  scope: string;
}

enum AuthMode {
  Popup = 'popup',
  Redirect = 'redirect'
}

Usage Examples

Protected Route

import { Navigate } from 'react-router-dom';
import { authClient } from './auth';

function ProtectedRoute({ children }) {
  const { user, isPending } = authClient.useAuth();

  if (isPending) return <div>Loading...</div>;
  if (!user) return <Navigate to="/login" />;

  return children;
}

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

Automatic Token Refresh

import { useEffect } from 'react';
import { authClient } from './auth';

function TokenRefreshManager() {
  useEffect(() => {
    const checkToken = async () => {
      const token = authClient.token();

      if (token) {
        const expiresIn = token.expires_in * 1000; // Convert to ms
        const now = Date.now();
        const timeUntilExpiry = expiresIn - now;

        // Refresh if expires in less than 5 minutes
        if (timeUntilExpiry < 5 * 60 * 1000) {
          await authClient.refreshToken();
        }
      }
    };

    // Check every minute
    const interval = setInterval(checkToken, 60000);
    checkToken(); // Initial check

    return () => clearInterval(interval);
  }, []);

  return null;
}

Error Handling

function LoginWithErrorHandling() {
  const [error, setError] = useState<string | null>(null);

  const handleLogin = async () => {
    try {
      const token = await authClient.signIn({
        authMode: AuthMode.Popup,
        callbackUrl: '/dashboard',
      });

      if (!token) {
        setError('Failed to sign in. Please try again.');
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
    }
  };

  return (
    <div>
      <button onClick={handleLogin}>Sign In</button>
      {error && <div className="error">{error}</div>}
    </div>
  );
}

Custom API Calls

function UserProfile() {
  const [profile, setProfile] = useState(null);

  useEffect(() => {
    const loadProfile = async () => {
      const { data, error } = await authClient.$fetch('/api/profile');

      if (!error) {
        setProfile(data);
      }
    };

    loadProfile();
  }, []);

  return <div>{profile?.bio}</div>;
}

Popup vs Redirect Mode

Popup Mode (Recommended for SPAs):

  • Better UX - no full page reload
  • Returns token directly in promise
  • Uses window.postMessage for communication
  • Requires popup blocker to be disabled
const token = await authClient.signIn({
  authMode: AuthMode.Popup,
  callbackUrl: '/dashboard',
});

Redirect Mode (Traditional):

  • Full page redirect to auth provider
  • Better compatibility with strict security policies
  • Requires callback page to handle token exchange
// Login page
authClient.signIn({
  authMode: AuthMode.Redirect,
  callbackUrl: '/dashboard',
});

// Callback page
useEffect(() => {
  const params = new URLSearchParams(window.location.search);
  authClient.getToken(params.get('code')!, params.get('state')!);
}, []);

Architecture

Inspired by better-auth's client design:

┌─────────────────────────────────────┐
│      createAuthClient()             │
│                                     │
│  ┌──────────────────────────────┐  │
│  │   Core Client                │  │
│  │   - OIDC/OAuth2 with PKCE    │  │
│  │   - Token management         │  │
│  │   - $fetch wrapper           │  │
│  └──────────────────────────────┘  │
│               ↓                     │
│  ┌──────────────────────────────┐  │
│  │   Zustand Store              │  │
│  │   - User info state          │  │
│  │   - Loading states           │  │
│  └──────────────────────────────┘  │
│               ↓                     │
│  ┌──────────────────────────────┐  │
│  │   React Hooks                │  │
│  │   - useUserinfo()            │  │
│  │   - useAuth()                │  │
│  └──────────────────────────────┘  │
└─────────────────────────────────────┘

See ARCHITECTURE.md for detailed architecture documentation.

Security

PKCE (Proof Key for Code Exchange)

This library implements OAuth 2.0 Authorization Code Flow with PKCE:

  1. Code Verifier: Random 64-character string
  2. Code Challenge: SHA-256 hash of verifier
  3. State Parameter: Random 32-character string for CSRF protection
  4. Secure Storage: Tokens in localStorage, PKCE params in sessionStorage

Best Practices

  1. Use HTTPS: Always use HTTPS in production
  2. Validate State: State parameter is automatically validated
  3. Token Storage: Tokens are stored securely in localStorage
  4. Origin Validation: Popup mode validates message origins
  5. Token Refresh: Implement automatic token refresh before expiry

TypeScript

Full type inference works out of the box:

const authClient = createAuthClient({
  baseURL: 'https://auth.example.com',
  clientId: 'my-client-id',
  redirectUri: '/callback',
});

// All methods are fully typed
type Client = typeof authClient;
// Client: {
//   signIn: (auth: {...}) => Promise<Token | null>
//   getUserinfo: () => Promise<User | null>
//   useUserinfo: () => {...}
//   ...
// }

Comparison with better-auth

Similarities:

  • Hooks attached to client object pattern
  • Full TypeScript support with type inference
  • Factory pattern API

Differences:

  • Focus: OIDC/OAuth2 client-side only (better-auth includes backend)
  • State Management: Zustand instead of nanostores
  • Simplicity: No plugin system, focused on OAuth2/OIDC
  • PKCE: Built-in PKCE support for browser-based apps
  • Popup Mode: Native popup authentication support

License

MIT

Contributing

Contributions welcome! Please submit issues and PRs to the repository.