Skip to main content

Quick Start

The createExpressAuthMiddleware function creates an Express middleware that automatically verifies Logto tokens and attaches user information to the request object.
import express from 'express';
import { createExpressAuthMiddleware } from '@ouim/logto-authkit/server';

const app = express();

const authMiddleware = createExpressAuthMiddleware({
  logtoUrl: 'https://your-tenant.logto.app',
  audience: 'https://api.yourapp.com',
});

// Protected route
app.get('/api/user/profile', authMiddleware, (req, res) => {
  // Access authenticated user info
  const { userId, payload } = req.auth;
  
  res.json({
    userId,
    email: payload.email,
    name: payload.name,
  });
});

app.listen(3000);

Configuration

The middleware accepts a VerifyAuthOptions object:
logtoUrl
string
required
Your Logto server URL
logtoUrl: 'https://your-tenant.logto.app'
audience
string
required
The API resource identifier you registered in Logto
audience: 'https://api.yourapp.com'
Custom cookie name if you changed it in the frontend
cookieName: 'my_custom_token'
requiredScope
string
Require a specific scope to be present in the token
requiredScope: 'read:admin'
allowGuest
boolean
default:"false"
Allow unauthenticated users with guest context
allowGuest: true

Request Object

The middleware adds an auth property to the Express request:
interface ExpressRequest {
  auth?: AuthContext;
  // ... other Express request properties
}

interface AuthContext {
  userId: string | null;          // Logto user ID
  isAuthenticated: boolean;       // true if authenticated
  payload: AuthPayload | null;    // Full JWT claims
  isGuest?: boolean;             // true in guest mode
  guestId?: string;              // UUID for guest users
}

Usage Examples

Basic Protected Route

import { createExpressAuthMiddleware } from '@ouim/logto-authkit/server';

const auth = createExpressAuthMiddleware({
  logtoUrl: process.env.LOGTO_URL,
  audience: process.env.LOGTO_API_RESOURCE,
});

app.get('/api/protected', auth, (req, res) => {
  res.json({ 
    message: 'This is protected!',
    userId: req.auth.userId 
  });
});

Route with Required Scope

const adminAuth = createExpressAuthMiddleware({
  logtoUrl: process.env.LOGTO_URL,
  audience: process.env.LOGTO_API_RESOURCE,
  requiredScope: 'admin:write',
});

app.delete('/api/admin/users/:id', adminAuth, (req, res) => {
  // Only users with 'admin:write' scope can access
  res.json({ success: true });
});

Multiple Middleware

Use different authentication requirements for different routes:
const basicAuth = createExpressAuthMiddleware({
  logtoUrl: process.env.LOGTO_URL,
  audience: process.env.LOGTO_API_RESOURCE,
});

const adminAuth = createExpressAuthMiddleware({
  logtoUrl: process.env.LOGTO_URL,
  audience: process.env.LOGTO_API_RESOURCE,
  requiredScope: 'admin',
});

// Regular authenticated routes
app.use('/api/user', basicAuth);
app.get('/api/user/profile', (req, res) => { /* ... */ });
app.put('/api/user/settings', (req, res) => { /* ... */ });

// Admin-only routes
app.use('/api/admin', adminAuth);
app.get('/api/admin/users', (req, res) => { /* ... */ });
app.post('/api/admin/settings', (req, res) => { /* ... */ });

Guest Mode

Allow both authenticated and guest users:
const flexibleAuth = createExpressAuthMiddleware({
  logtoUrl: process.env.LOGTO_URL,
  audience: process.env.LOGTO_API_RESOURCE,
  allowGuest: true,
});

app.get('/api/cart', flexibleAuth, (req, res) => {
  if (req.auth.isAuthenticated) {
    // Load user's saved cart from database
    const cart = await getCartByUserId(req.auth.userId);
    res.json(cart);
  } else {
    // Load guest cart from session
    const cart = await getCartByGuestId(req.auth.guestId);
    res.json(cart);
  }
});

TypeScript Usage

import express, { Request, Response } from 'express';
import { createExpressAuthMiddleware, AuthContext } from '@ouim/logto-authkit/server';

// Extend Express Request type
declare global {
  namespace Express {
    interface Request {
      auth?: AuthContext;
    }
  }
}

const app = express();

const authMiddleware = createExpressAuthMiddleware({
  logtoUrl: process.env.LOGTO_URL!,
  audience: process.env.LOGTO_API_RESOURCE!,
});

app.get('/api/profile', authMiddleware, (req: Request, res: Response) => {
  // TypeScript knows about req.auth
  const userId = req.auth!.userId;
  res.json({ userId });
});

Error Responses

The middleware returns 401 Unauthorized for authentication failures:

Missing Token

{
  "error": "Authentication required",
  "message": "No token found in cookies or Authorization header"
}

Invalid Token

{
  "error": "Authentication failed",
  "message": "Token verification failed: Token has expired"
}

Missing Scope

{
  "error": "Authentication failed",
  "message": "Token verification failed: Missing required scope: admin:write"
}
When allowGuest: true, the middleware never returns 401 errors. Instead, it sets req.auth with guest context.

How It Works

The middleware performs these steps on each request:
1

Parse Cookies

Automatically parses cookies using cookie-parser if not already available
2

Extract Token

Checks for token in cookies (logto_authtoken) then Authorization header
3

Verify Token

  • Fetches JWKS from Logto server (cached for 5 minutes)
  • Verifies JWT signature using the appropriate public key
  • Validates issuer, audience, expiration, and scopes
4

Set Auth Context

Attaches AuthContext to req.auth and calls next()
5

Handle Errors

Returns 401 JSON response or guest context (if allowGuest enabled)

Best Practices

Store configuration in environment variables:
const auth = createExpressAuthMiddleware({
  logtoUrl: process.env.LOGTO_URL,
  audience: process.env.LOGTO_API_RESOURCE,
});
Use different middleware instances for different authorization levels:
const userAuth = createExpressAuthMiddleware({ ... });
const adminAuth = createExpressAuthMiddleware({ 
  ..., 
  requiredScope: 'admin' 
});
Always check isAuthenticated when using allowGuest:
app.post('/api/checkout', flexibleAuth, (req, res) => {
  if (!req.auth.isAuthenticated) {
    return res.status(401).json({ 
      error: 'Please login to checkout' 
    });
  }
  // Process checkout
});
The full JWT payload is available in req.auth.payload:
app.get('/api/user', auth, (req, res) => {
  const { email, name, picture } = req.auth.payload;
  res.json({ email, name, picture });
});
The middleware automatically handles cookie parsing:
  • If req.cookies exists (already parsed), uses it directly
  • If not, applies cookie-parser middleware internally
  • No need to add cookie-parser to your app separately
// This works even without app.use(cookieParser())
app.get('/api/protected', authMiddleware, (req, res) => {
  res.json({ userId: req.auth.userId });
});
While the middleware handles cookie parsing internally, you can still use cookie-parser globally if needed for other routes.

Next.js Integration

Server-side auth for Next.js

Generic Usage

Use in any Node.js environment