@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
Maintainers
Readme
@evup/auth-client
Production-ready OAuth 2.1 authentication for Next.js with automatic token refresh, PKCE, and backchannel logout support.
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-client2. 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:3000Getting Client Credentials:
- Go to your Evup Auth admin panel:
https://auth.yourapp.com/admin/oauth-clients - Click "Create OAuth Client"
- 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)
- Redirect URI:
- Save and copy your
client_idandclient_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 loginGET /api/auth/callback- Handle OAuth callbackPOST /api/auth/logout- User logoutPOST /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? } | nullrequireAuth()
Require authentication, redirects to login if not authenticated.
const user = await requireAuth(); // Never returns nullgetSession()
Get full session including tokens.
const session = await getSession();
// session: { accessToken, refreshToken?, idToken, expiresAt, user } | nullgetUserFromToken(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 redirectuseAuth()
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/loginGET /api/auth/callbackPOST /api/auth/logoutPOST /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_accessin scopes - User must have a valid refresh token
scopes: ['openid', 'profile', 'email', 'offline_access'] // ← RequiredIf 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:
- User clicks "Sign out" in any app (or from auth server dashboard)
- Auth server sends signed JWT to each client's
backchannel_logout_uri - Client SDK validates JWT and revokes all user sessions
- Next time user makes a request, they're automatically logged out
Setup:
Configure Backchannel Logout URI when creating OAuth client:
Use catch-all handler:
// app/api/auth/[...all]/route.ts const handler = createCatchAllHandler(authConfig); export { handler as GET, handler as POST };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 pickerprompt: '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 thisToken refresh not working
Add offline_access to scopes:
scopes: ['openid', 'profile', 'email', 'offline_access']Backchannel logout not working
- Check backchannel logout URI is configured in auth server
- Verify URI is publicly accessible from auth server
- Ensure client secret matches (used for JWT verification)
- 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 validationDesign 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
