Package Exports
- @sp-uvb/next
- @sp-uvb/next/server
Readme
@sp-uvb/next
Next.js middleware for Universal Verification Broker (UVB) authentication. Supports both App Router and Pages Router.
Installation
npm install @sp-uvb/next
# or
yarn add @sp-uvb/next
# or
pnpm add @sp-uvb/nextQuick Start
App Router (Next.js 13+)
Create middleware.ts in your project root:
import { createUvbMiddleware } from '@sp-uvb/next';
export default createUvbMiddleware({
tenantId: 'my-tenant',
uvbUrl: 'http://localhost:8080',
excludePaths: ['/login', '/api/auth'],
loginUrl: '/login', // Optional: redirect to login page
});
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};Access session in Server Components:
import { getUvbSessionServer } from '@sp-uvb/next/server';
export default async function ProfilePage() {
const session = await getUvbSessionServer();
return (
<div>
<h1>User Profile</h1>
<p>User ID: {session?.userId}</p>
<p>Factors: {session?.factorsVerified.join(', ')}</p>
</div>
);
}Pages Router (Next.js 12+)
Protect API routes:
// pages/api/profile.ts
import { requireUvbAuth, getUvbSession } from '@sp-uvb/next/server';
export default requireUvbAuth(async function handler(req, res) {
const session = getUvbSession(req);
res.json({
userId: session.userId,
factors: session.factorsVerified,
});
});API
App Router
createUvbMiddleware(config)
Create Next.js middleware for App Router.
Configuration:
tenantId(required): Your UVB tenant IDuvbUrl(optional): UVB server URL, defaults tohttp://localhost:8080apiKey(optional): API key for server-to-server authenticationcookieName(optional): Cookie name for session token, defaults touvb_sessionexcludePaths(optional): Array of paths to exclude from authenticationloginUrl(optional): Redirect URL for unauthenticated users
// middleware.ts
import { createUvbMiddleware } from '@sp-uvb/next';
export default createUvbMiddleware({
tenantId: 'my-tenant',
uvbUrl: process.env.UVB_URL,
loginUrl: '/login',
});getUvbSessionServer()
Get session in App Router Server Components.
import { getUvbSessionServer } from '@sp-uvb/next/server';
export default async function Page() {
const session = await getUvbSessionServer();
if (!session) {
return <div>Not authenticated</div>;
}
return <div>Hello {session.userId}</div>;
}requireFactors(factors)
Middleware to require specific MFA factors in App Router.
// middleware.ts
import { createUvbMiddleware, requireFactors } from '@sp-uvb/next';
import { NextResponse } from 'next/server';
const uvbMiddleware = createUvbMiddleware({
tenantId: 'my-tenant',
});
const adminFactors = requireFactors(['totp', 'webauthn']);
export async function middleware(request) {
// Apply base auth
const response = await uvbMiddleware(request);
if (response.status !== 200) return response;
// Require additional factors for /admin
if (request.nextUrl.pathname.startsWith('/admin')) {
return adminFactors(request);
}
return NextResponse.next();
}Pages Router / Server-Side
getUvbSession(req)
Get session from API route or getServerSideProps.
import { getUvbSession } from '@sp-uvb/next/server';
// In API route
export default function handler(req, res) {
const session = getUvbSession(req);
if (!session) {
return res.status(401).json({ error: 'Not authenticated' });
}
res.json({ userId: session.userId });
}
// In getServerSideProps
export async function getServerSideProps({ req }) {
const session = getUvbSession(req);
if (!session) {
return { redirect: { destination: '/login', permanent: false } };
}
return { props: { session } };
}requireUvbAuth(handler)
HOF to require authentication for API routes.
import { requireUvbAuth } from '@sp-uvb/next/server';
export default requireUvbAuth(async function handler(req, res) {
// User is authenticated
res.json({ message: 'Protected data' });
});requireFactorsApi(factors)
HOF to require specific MFA factors for API routes.
import { requireFactorsApi } from '@sp-uvb/next/server';
export default requireFactorsApi(['totp', 'webauthn'])(async function handler(req, res) {
res.json({ message: 'Admin action completed' });
});validateUvbSession(token, tenantId, uvbUrl?, apiKey?)
Validate a session token directly (for custom flows).
import { validateUvbSession } from '@sp-uvb/next/server';
export default async function handler(req, res) {
const token = req.cookies.uvb_session;
const session = await validateUvbSession(token, 'my-tenant', 'http://localhost:8080');
if (!session) {
return res.status(401).json({ error: 'Invalid session' });
}
res.json({ userId: session.userId });
}Examples
Protecting Specific Routes (App Router)
// middleware.ts
import { createUvbMiddleware } from '@sp-uvb/next';
export default createUvbMiddleware({
tenantId: 'my-tenant',
excludePaths: ['/login', '/register', '/api/auth', '/public'],
});
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*', '/profile/:path*'],
};Conditional MFA Requirements
// middleware.ts
import { createUvbMiddleware, requireFactors } from '@sp-uvb/next';
import { NextResponse } from 'next/server';
const baseAuth = createUvbMiddleware({ tenantId: 'my-tenant' });
const strictAuth = requireFactors(['totp', 'webauthn']);
export async function middleware(request) {
const response = await baseAuth(request);
if (response.status !== 200) return response;
// Require strong auth for sensitive routes
if (
request.nextUrl.pathname.startsWith('/admin') ||
request.nextUrl.pathname.startsWith('/transfer')
) {
return strictAuth(request);
}
return NextResponse.next();
}Server Actions (App Router)
// app/actions.ts
'use server';
import { getUvbSessionServer } from '@sp-uvb/next/server';
export async function updateProfile(formData: FormData) {
const session = await getUvbSessionServer();
if (!session) {
throw new Error('Not authenticated');
}
// Update user profile using session.userId
// ...
}API Route with Factor Requirements (Pages Router)
// pages/api/admin/delete.ts
import { requireFactorsApi, getUvbSession } from '@sp-uvb/next/server';
export default requireFactorsApi(['totp', 'webauthn'])(async function handler(req, res) {
const session = getUvbSession(req);
// Perform admin action
res.json({ message: `Admin ${session.userId} performed action` });
});Custom Authentication Flow
// pages/api/custom-auth.ts
import { validateUvbSession } from '@sp-uvb/next/server';
export default async function handler(req, res) {
const customToken = req.headers['x-custom-token'];
if (!customToken) {
return res.status(401).json({ error: 'No token' });
}
const session = await validateUvbSession(customToken, 'my-tenant', process.env.UVB_URL);
if (!session) {
return res.status(401).json({ error: 'Invalid token' });
}
res.json({ userId: session.userId });
}Redirect After Login
// middleware.ts
import { createUvbMiddleware } from '@sp-uvb/next';
export default createUvbMiddleware({
tenantId: 'my-tenant',
loginUrl: '/login' // Redirects to /login?from=/original-path
});
// pages/login.tsx
import { useRouter } from 'next/router';
export default function LoginPage() {
const router = useRouter();
const { from } = router.query;
const handleLogin = async () => {
// Perform login
// ...
// Redirect back to original page
router.push((from as string) || '/dashboard');
};
return <button onClick={handleLogin}>Login</button>;
}Server Component with Auth Check
// app/profile/page.tsx
import { getUvbSessionServer } from '@sp-uvb/next/server';
import { redirect } from 'next/navigation';
export default async function ProfilePage() {
const session = await getUvbSessionServer();
if (!session) {
redirect('/login');
}
// Check for specific factors
const hasStrongAuth = session.factorsVerified.includes('totp') &&
session.factorsVerified.includes('webauthn');
return (
<div>
<h1>Profile</h1>
<p>User ID: {session.userId}</p>
<p>Strong Auth: {hasStrongAuth ? 'Yes' : 'No'}</p>
</div>
);
}TypeScript
This package includes full TypeScript definitions:
import type { UvbSession, UvbMiddlewareConfig } from '@sp-uvb/next';License
MIT