Skip to main content

Quick Start

Use verifyNextAuth to authenticate Next.js App Router API routes and middleware. It handles both cookies and Authorization headers.
import { NextRequest, NextResponse } from 'next/server';
import { verifyNextAuth } from '@ouim/logto-authkit/server';

export async function GET(request: NextRequest) {
  const result = await verifyNextAuth(request, {
    logtoUrl: process.env.LOGTO_URL!,
    audience: process.env.LOGTO_API_RESOURCE!,
  });

  if (!result.success) {
    return NextResponse.json(
      { error: result.error },
      { status: 401 }
    );
  }

  const { userId, payload } = result.auth;
  
  return NextResponse.json({
    userId,
    email: payload.email,
  });
}

Function Signature

function verifyNextAuth(
  request: NextRequest,
  options: VerifyAuthOptions
): Promise<
  | { success: true; auth: AuthContext }
  | { success: false; error: string; auth?: AuthContext }
>

Parameters

request
NextRequest
required
The Next.js request object from your API route or middleware
options
VerifyAuthOptions
required
Configuration options for authentication

Return Value

success
boolean
required
Whether authentication was successful
auth
AuthContext
User authentication context (always present when success: true, optional with guest mode)
error
string
Error message when success: false

Usage Examples

API Route (App Router)

// app/api/user/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyNextAuth } from '@ouim/logto-authkit/server';

export async function GET(request: NextRequest) {
  const result = await verifyNextAuth(request, {
    logtoUrl: process.env.LOGTO_URL!,
    audience: process.env.LOGTO_API_RESOURCE!,
  });

  if (!result.success) {
    return NextResponse.json(
      { error: result.error },
      { status: 401 }
    );
  }

  // Fetch user data from database
  const user = await db.user.findUnique({
    where: { id: result.auth.userId },
  });

  return NextResponse.json({ user });
}

POST Route with Data

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyNextAuth } from '@ouim/logto-authkit/server';

export async function POST(request: NextRequest) {
  const result = await verifyNextAuth(request, {
    logtoUrl: process.env.LOGTO_URL!,
    audience: process.env.LOGTO_API_RESOURCE!,
  });

  if (!result.success) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }

  const body = await request.json();
  
  const post = await db.post.create({
    data: {
      ...body,
      authorId: result.auth.userId,
    },
  });

  return NextResponse.json({ post });
}

Route with Required Scope

// app/api/admin/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyNextAuth } from '@ouim/logto-authkit/server';

export async function DELETE(request: NextRequest) {
  const result = await verifyNextAuth(request, {
    logtoUrl: process.env.LOGTO_URL!,
    audience: process.env.LOGTO_API_RESOURCE!,
    requiredScope: 'admin:users:delete',
  });

  if (!result.success) {
    return NextResponse.json(
      { error: 'Insufficient permissions' },
      { status: 403 }
    );
  }

  // Delete user logic
  return NextResponse.json({ success: true });
}

Next.js Middleware

Protect multiple routes with Next.js middleware:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyNextAuth } from '@ouim/logto-authkit/server';

export async function middleware(request: NextRequest) {
  const result = await verifyNextAuth(request, {
    logtoUrl: process.env.LOGTO_URL!,
    audience: process.env.LOGTO_API_RESOURCE!,
  });

  if (!result.success) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }

  // Add user ID to response headers for downstream use
  const response = NextResponse.next();
  response.headers.set('x-user-id', result.auth.userId!);
  
  return response;
}

export const config = {
  matcher: '/api/protected/:path*',
};

Guest Mode

Allow both authenticated and guest users:
// app/api/cart/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyNextAuth } from '@ouim/logto-authkit/server';

export async function GET(request: NextRequest) {
  const result = await verifyNextAuth(request, {
    logtoUrl: process.env.LOGTO_URL!,
    audience: process.env.LOGTO_API_RESOURCE!,
    allowGuest: true,
  });

  // result.auth is always present with allowGuest
  const cart = result.auth!.isAuthenticated
    ? await getCartByUserId(result.auth.userId!)
    : await getCartByGuestId(result.auth.guestId!);

  return NextResponse.json({ cart });
}

Reusable Auth Helper

Create a helper function for consistent authentication:
// lib/auth.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyNextAuth, AuthContext } from '@ouim/logto-authkit/server';

type AuthHandler = (
  request: NextRequest,
  auth: AuthContext
) => Promise<NextResponse> | NextResponse;

export function withAuth(handler: AuthHandler) {
  return async (request: NextRequest) => {
    const result = await verifyNextAuth(request, {
      logtoUrl: process.env.LOGTO_URL!,
      audience: process.env.LOGTO_API_RESOURCE!,
    });

    if (!result.success) {
      return NextResponse.json(
        { error: result.error },
        { status: 401 }
      );
    }

    return handler(request, result.auth);
  };
}

// Usage in routes
// app/api/profile/route.ts
import { withAuth } from '@/lib/auth';

export const GET = withAuth(async (request, auth) => {
  const user = await db.user.findUnique({
    where: { id: auth.userId },
  });
  
  return NextResponse.json({ user });
});

Server Actions (Experimental)

For Next.js Server Actions, extract the token from cookies:
'use server'

import { cookies } from 'next/headers';
import { verifyAuth } from '@ouim/logto-authkit/server';

export async function updateProfile(formData: FormData) {
  const cookieStore = cookies();
  const token = cookieStore.get('logto_authtoken')?.value;

  if (!token) {
    throw new Error('Unauthorized');
  }

  const auth = await verifyAuth(token, {
    logtoUrl: process.env.LOGTO_URL!,
    audience: process.env.LOGTO_API_RESOURCE!,
  });

  // Update user profile
  await db.user.update({
    where: { id: auth.userId },
    data: {
      name: formData.get('name'),
    },
  });
}
For Server Actions, use the generic verifyAuth function instead of verifyNextAuth. See Generic Usage for details.

Error Handling

With allowGuest: false (default)

const result = await verifyNextAuth(request, { ... });

if (!result.success) {
  // result.error contains the error message
  // result.auth is undefined
  return NextResponse.json(
    { error: result.error },
    { status: 401 }
  );
}

// result.auth is guaranteed to exist and be authenticated
const userId = result.auth.userId;

With allowGuest: true

const result = await verifyNextAuth(request, {
  allowGuest: true,
  // ... other options
});

if (result.success) {
  // Authenticated user
  const userId = result.auth.userId;
} else {
  // Guest user (result.auth contains guest context)
  if (result.auth?.isGuest) {
    const guestId = result.auth.guestId;
  }
}

Common Error Messages

No authentication token was provided in the request.Solution: Ensure the frontend is setting cookies or sending Authorization header.
The JWT token’s expiration time has passed.Solution: Refresh the token on the frontend or prompt user to re-authenticate.
Token wasn’t issued by your Logto server.Solution: Verify logtoUrl matches your Logto tenant URL.
Token’s audience claim doesn’t match your API resource.Solution: Verify audience matches the API resource registered in Logto.
Token doesn’t include the required scope.Solution: Ensure the scope is requested during frontend authentication.

Best Practices

1

Use Environment Variables

Store Logto configuration in environment variables:
const result = await verifyNextAuth(request, {
  logtoUrl: process.env.LOGTO_URL!,
  audience: process.env.LOGTO_API_RESOURCE!,
});
2

Create Helper Functions

Wrap verifyNextAuth in helper functions for consistent error handling:
export const withAuth = (handler: AuthHandler) => { ... };
3

Return Appropriate Status Codes

  • 401 Unauthorized: Authentication failed or missing
  • 403 Forbidden: Authenticated but insufficient permissions
4

Validate Auth Context

Always check isAuthenticated when using guest mode:
if (!result.auth?.isAuthenticated) {
  return NextResponse.json({ error: 'Login required' }, { status: 401 });
}

Express Middleware

Middleware for Express.js applications

Generic Usage

Flexible verifyAuth for any environment