JSPM

@sp-uvb/next

0.1.0
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 3
    • Score
      100M100P100Q23059F
    • License MIT

    Next.js middleware for Universal Verification Broker (UVB)

    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/next

    Quick 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 ID
    • uvbUrl (optional): UVB server URL, defaults to http://localhost:8080
    • apiKey (optional): API key for server-to-server authentication
    • cookieName (optional): Cookie name for session token, defaults to uvb_session
    • excludePaths (optional): Array of paths to exclude from authentication
    • loginUrl (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