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

@evup/auth-client

v1.1.0

Published

Universal OAuth 2.1 client SDK for Evup Auth with automatic token refresh, PKCE, and multi-framework support

Readme

@evup/auth-client

Production-ready OAuth 2.1 authentication for Next.js with automatic token refresh, PKCE, and backchannel logout support.

npm version License: MIT

Why This Library?

  • 2 Files to Auth - Create config + one route file, you're done
  • Automatic Token Refresh - Tokens refresh 5 minutes before expiry
  • Backchannel Logout - Logout from auth server logs out all client apps
  • OAuth 2.1 Compliant - PKCE, state validation, secure token handling
  • TypeScript Native - Full type safety out of the box
  • Zero UI Lock-in - Bring your own components

Quick Start

1. Install

npm install @evup/auth-client

2. Setup Environment Variables

Create a .env.local file in your project root:

# OAuth Client Credentials (get from auth server admin panel)
EVUP_CLIENT_ID=your_client_id
EVUP_CLIENT_SECRET=your_client_secret

# Auth Server URL
EVUP_ISSUER=https://auth.yourapp.com

# Your Application URL
NEXT_PUBLIC_APP_URL=http://localhost:3000

Getting Client Credentials:

  1. Go to your Evup Auth admin panel: https://auth.yourapp.com/admin/oauth-clients
  2. Click "Create OAuth Client"
  3. Configure redirect URIs:
    • Redirect URI: http://localhost:3000/api/auth/callback
    • Post-logout Redirect URI: http://localhost:3000 (optional)
    • Backchannel Logout URI: http://localhost:3000/api/auth/backchannel-logout (optional, for automatic logout)
  4. Save and copy your client_id and client_secret (shown only once)

3. Create Config

// evup-auth.config.ts
import { initializeAuth } from '@evup/auth-client/nextjs';

export const authConfig = {
  clientId: process.env.EVUP_CLIENT_ID!,
  clientSecret: process.env.EVUP_CLIENT_SECRET!,
  issuer: process.env.EVUP_ISSUER!,
  appUrl: process.env.NEXT_PUBLIC_APP_URL!,
  scopes: ['openid', 'profile', 'email', 'offline_access'],
};

initializeAuth(authConfig);

4. Create Auth Route

// app/api/auth/[...all]/route.ts
import { createCatchAllHandler } from '@evup/auth-client/nextjs';
import { authConfig } from '@/evup-auth.config';

const handler = createCatchAllHandler(authConfig);

export { handler as GET, handler as POST };

This single file handles:

  • GET /api/auth/login - Initiate login
  • GET /api/auth/callback - Handle OAuth callback
  • POST /api/auth/logout - User logout
  • POST /api/auth/backchannel-logout - Receive logout notifications from auth server

5. Use in Components

Server Component:

// app/page.tsx
import { getUser } from '@evup/auth-client/nextjs';

export default async function HomePage() {
  const user = await getUser();

  if (!user) {
    return <a href="/api/auth/login">Sign in</a>;
  }

  return (
    <div>
      <h1>Hello, {user.name}</h1>
      <p>Email: {user.email}</p>
      <form action="/api/auth/logout" method="POST">
        <button>Sign out</button>
      </form>
    </div>
  );
}

Protected Route:

// app/dashboard/page.tsx
import { requireAuth } from '@evup/auth-client/nextjs';

export default async function DashboardPage() {
  const user = await requireAuth(); // Redirects to login if not authenticated

  return <div>Welcome, {user.name}!</div>;
}

Client Component:

// components/auth-buttons.tsx
'use client';

import { useAuth } from '@evup/auth-client/nextjs';

export function AuthButtons() {
  const { login, logout } = useAuth();

  return (
    <>
      <button onClick={() => login()}>Sign in</button>
      <button onClick={() => logout()}>Sign out</button>
    </>
  );
}

API Reference

Server Functions

import { getUser, requireAuth, getSession } from '@evup/auth-client/nextjs';

getUser()

Get current user, returns null if not authenticated.

const user = await getUser();
// user: { id, email, name, image?, roles?, permissions? } | null

requireAuth()

Require authentication, redirects to login if not authenticated.

const user = await requireAuth(); // Never returns null

getSession()

Get full session including tokens.

const session = await getSession();
// session: { accessToken, refreshToken?, idToken, expiresAt, user } | null

getUserFromToken(token, config)

Verify a Bearer token and get user info. Useful for API routes.

import { getUserFromToken } from '@evup/auth-client/nextjs';
import { authConfig } from '@/evup-auth.config';

const token = request.headers.get('Authorization')?.replace('Bearer ', '');
const user = await getUserFromToken(token!, authConfig);

Client Functions

import { authClient, useAuth } from '@evup/auth-client/nextjs';

authClient.login(options?)

Navigate to login page.

authClient.login(); // Simple login
authClient.login({ redirectTo: '/dashboard' }); // With redirect
authClient.login({ prompt: 'login' }); // Force re-auth (account switching)

authClient.logout(options?)

Log out and redirect.

authClient.logout(); // Simple logout
authClient.logout({ redirectTo: '/' }); // With redirect

useAuth()

React hook for authentication actions.

const { login, logout } = useAuth();

Route Handlers

import { createCatchAllHandler, createAuthHandlers } from '@evup/auth-client/nextjs';

createCatchAllHandler(config) - Recommended

Creates a single handler for all auth routes (requires Next.js 15+).

// app/api/auth/[...all]/route.ts
const handler = createCatchAllHandler(authConfig);
export { handler as GET, handler as POST };

Handles:

  • GET /api/auth/login
  • GET /api/auth/callback
  • POST /api/auth/logout
  • POST /api/auth/backchannel-logout

createAuthHandlers(config) - Advanced

For individual route handlers (Next.js 14+):

const { loginHandler, callbackHandler, logoutHandler, backchannelLogoutHandler } =
  createAuthHandlers(authConfig);

Configuration Options

interface EvupAuthConfig {
  clientId: string;           // OAuth client ID
  clientSecret: string;       // OAuth client secret
  issuer: string;             // Auth server URL
  appUrl: string;             // Your app URL
  scopes?: string[];          // Default: ['openid', 'profile', 'email']

  redirects?: {
    afterLogin?: string;      // Default: '/'
    afterLogout?: string;     // Default: '/'
    onError?: string;         // Default: '/auth/error'
  };

  defaultPrompt?: 'login' | 'select_account' | 'consent';
  // Control login behavior:
  // - undefined: Use SSO session if available (default)
  // - 'login': Always force re-authentication
  // - 'select_account': Show account picker
  // - 'consent': Always show consent screen
}

Features

Automatic Token Refresh

Tokens are automatically refreshed 5 minutes before expiration when you call getUser() or getSession().

Requirements:

  • Include offline_access in scopes
  • User must have a valid refresh token
scopes: ['openid', 'profile', 'email', 'offline_access'] // ← Required

If refresh fails, the session is cleared and user must re-login.

Backchannel Logout (Automatic Multi-App Logout)

When a user logs out from the auth server, ALL client applications are automatically logged out via backchannel logout notifications.

How it works:

  1. User clicks "Sign out" in any app (or from auth server dashboard)
  2. Auth server sends signed JWT to each client's backchannel_logout_uri
  3. Client SDK validates JWT and revokes all user sessions
  4. Next time user makes a request, they're automatically logged out

Setup:

  1. Configure Backchannel Logout URI when creating OAuth client:

  2. Use catch-all handler:

    // app/api/auth/[...all]/route.ts
    const handler = createCatchAllHandler(authConfig);
    export { handler as GET, handler as POST };
  3. That's it! The SDK handles everything automatically.

Automatic Session Validation

The SDK validates the session on each request through middleware, ensuring:

  • User profile data is always fresh
  • Sessions are automatically invalidated when tokens are revoked

Account Switching

Allow users to switch between accounts using the prompt parameter:

'use client';

import { useAuth } from '@evup/auth-client/nextjs';

export function SwitchAccountButton() {
  const { login } = useAuth();

  return (
    <button onClick={() => login({ prompt: 'login' })}>
      Sign in with different account
    </button>
  );
}

Prompt options:

  • prompt: 'login' - Force re-authentication (ignore SSO)
  • prompt: 'select_account' - Show account picker
  • prompt: 'consent' - Force consent screen

Security Features

  • PKCE (S256) - Authorization code interception protection
  • State Parameter - CSRF protection during OAuth flow
  • HttpOnly Cookies - Session tokens not accessible via JavaScript
  • Secure Flag - Cookies only sent over HTTPS in production
  • SameSite=Lax - Additional CSRF protection
  • ID Token Verification - JWKS-based signature validation
  • Token Revocation - Tokens revoked on logout (RFC 7009)
  • Automatic Session Validation - Sessions validated on each request

SSO Behavior

  • Without prompt: User automatically logged in if they have an active SSO session
  • With prompt: 'login': User sees login page even if they have an SSO session
  • On logout: Local session cleared, but SSO session remains (other apps stay logged in)
  • Sessions are validated on each request - revoked tokens will be detected automatically

Common Use Cases

API Route with Bearer Token

// app/api/protected/route.ts
import { getUserFromToken } from '@evup/auth-client/nextjs';
import { authConfig } from '@/evup-auth.config';

export async function GET(request: Request) {
  const token = request.headers.get('Authorization')?.replace('Bearer ', '');

  if (!token) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const user = await getUserFromToken(token, authConfig);

  if (!user) {
    return Response.json({ error: 'Invalid token' }, { status: 401 });
  }

  return Response.json({ user });
}

Custom Redirect After Login

<button onClick={() => login({ redirectTo: '/dashboard' })}>
  Sign in
</button>

Force Account Selection

<button onClick={() => login({ prompt: 'select_account' })}>
  Choose account
</button>

Check User Roles/Permissions

// app/admin/page.tsx
import { requireAuth } from '@evup/auth-client/nextjs';

export default async function AdminPage() {
  const user = await requireAuth();

  if (!user.roles?.includes('admin')) {
    return <div>Access denied</div>;
  }

  return <div>Admin panel</div>;
}

Requirements

  • Node.js 18+
  • Next.js 14+ (individual handlers) or 15+ (catch-all handler)

Troubleshooting

"Evup Auth not initialized"

Add initializeAuth(authConfig) to your config file:

// evup-auth.config.ts
import { initializeAuth } from '@evup/auth-client/nextjs';

export const authConfig = { /* ... */ };
initializeAuth(authConfig); // ← Add this

Token refresh not working

Add offline_access to scopes:

scopes: ['openid', 'profile', 'email', 'offline_access']

Backchannel logout not working

  1. Check backchannel logout URI is configured in auth server
  2. Verify URI is publicly accessible from auth server
  3. Ensure client secret matches (used for JWT verification)
  4. Check logs for JWT verification errors

TypeScript errors after install

Restart TypeScript server: Cmd+Shift+P → "TypeScript: Restart TS Server"

Session cookies not clearing after logout

This is expected behavior in Next.js Server Components (cannot modify cookies). The session is marked as invalid and will be cleared on next user action. To force immediate clearing, implement Next.js middleware.

Advanced

Individual Route Handlers

For Next.js 14 or custom routing:

// app/api/auth/login/route.ts
import { createAuthHandlers } from '@evup/auth-client/nextjs';
import { authConfig } from '@/evup-auth.config';

const { loginHandler } = createAuthHandlers(authConfig);

export async function GET(request: Request) {
  return loginHandler(request);
}
// app/api/auth/callback/route.ts
const { callbackHandler } = createAuthHandlers(authConfig);

export async function GET(request: Request) {
  return callbackHandler(request);
}
// app/api/auth/logout/route.ts
const { logoutHandler } = createAuthHandlers(authConfig);

export async function POST() {
  return logoutHandler();
}
// app/api/auth/backchannel-logout/route.ts
const { backchannelLogoutHandler } = createAuthHandlers(authConfig);

export async function POST(request: Request) {
  return backchannelLogoutHandler(request);
}

Error Handling

import { OAuthError, TokenRefreshError } from '@evup/auth-client/nextjs';

try {
  const session = await getSession();
} catch (error) {
  if (error instanceof TokenRefreshError) {
    // Handle token refresh failure
    console.error('Token expired, user needs to re-login');
  } else if (error instanceof OAuthError) {
    // Handle OAuth errors
    console.error('OAuth error:', error.message);
  }
}

Core OAuth Operations

For framework-agnostic use cases:

import {
  exchangeCodeForTokens,
  getUserInfo,
  verifyIdToken,
  refreshAccessToken,
  revokeToken,
} from '@evup/auth-client/core';

Architecture

@evup/auth-client/
├── core/          # Framework-agnostic OAuth logic
│   └── oauth.ts   # PKCE, token exchange, refresh, revoke
└── nextjs/        # Next.js App Router adapter
    ├── server.ts  # getUser, requireAuth, getSession, refreshUser
    ├── client.ts  # useAuth, authClient
    ├── routes.ts  # Route handlers (login, callback, logout, refresh-user, validate-session)
    └── middleware.ts  # Automatic session validation

Design Philosophy:

  • Core logic is framework-agnostic
  • Adapters wrap core logic with framework conventions
  • Future-proof: Adding new frameworks only requires a new adapter

License

MIT - Copyright (c) 2026 Evup Team

Support