Koa.js Middleware Chain for Authentication and Token Management in Lemon AI

Lemon AI implements a deterministic Koa.js middleware pipeline that extracts bearer tokens from HTTP headers, stores them in a global singleton, and routes requests through an extensible authentication gate before dispatching to API handlers.

The open-source Lemon AI project (hexdocom/lemonai) leverages Koa 2 to build its HTTP server, utilizing a carefully ordered middleware chain to handle authentication and token management. This architecture separates concerns between request preprocessing, token extraction, and authorization gating, making it straightforward to swap stub implementations with production-grade security logic.

Middleware Pipeline Architecture

The entry point at src/app.js registers eleven middleware layers in a specific sequence. This ordering ensures that request bodies are parsed and logging is active before authentication logic executes.

The pipeline flows as follows:

  1. Response WrappingwrapContext from src/middlewares/wrap.context.js attaches ctx.response.success, ctx.response.fail, and ctx.response.file helpers to every context.
  2. Body ParsingkoaBody handles multipart and JSON payloads, populating ctx.request.body.
  3. JSON Formatting – Pretty-prints JSON responses.
  4. Request Loggingkoa-logger and a custom URL logger output method and path to the console.
  5. Static Assetskoa-static serves files from the public directory.
  6. Timing – Measures and logs request latency.
  7. Token ExtractionsetGlobalTokenMiddleware reads the Authorization header.
  8. Authentication GateauthMiddleware validates (or stubs) the token and sets ctx.state.user.
  9. Routing – Dispatches to feature routers under /api/.
  10. API Documentation – Serves Swagger UI via koaSwagger.

Token Extraction and Global Storage

Before the authentication gate executes, the setGlobalTokenMiddleware in src/middlewares/setGlobalToken.js intercepts the request to extract bearer tokens.

The middleware checks for an Authorization header beginning with Bearer , splits the token value, and stores it in a global singleton defined in src/globals.js:

// src/middlewares/setGlobalToken.js
const globals = require('@src/globals');

module.exports = () => {
  return async (ctx, next) => {
    const authHeader = ctx.request.headers.authorization;
    if (authHeader && authHeader.startsWith('Bearer ')) {
      const token = authHeader.split(' ')[1];
      globals.setToken(token);
    }
    await next();
  };
};

The globals.js module provides a simple state container:

// src/globals.js
let currentToken = null;

module.exports = {
  setToken: (token) => { currentToken = token; },
  getToken: () => currentToken,
};

This design allows downstream middleware and route handlers to access the token via globals.getToken() without parsing headers repeatedly.

Authentication Gate Implementation

The authMiddleware in src/middlewares/auth.js serves as the security checkpoint. In the current implementation, it acts as a stub that assigns a default user object while skipping actual token validation.

The middleware defines an excludePatterns array for paths that should bypass authentication:

// src/middlewares/auth.js
const excludePatterns = [
  '/api/agent_store/last/'
];

module.exports = () => {
  return async (ctx, next) => {
    // Stub: always sets a default user
    ctx.state.user = { id: 1 };
    await next();
  };
};

While the stub currently ignores the excludePatterns array, the structure is in place to support conditional bypassing. The ctx.state.user object becomes available to all subsequent route handlers, enabling user-scoped logic.

Extending the Chain for Production Authentication

To replace the stub with real JWT or OAuth validation, modify src/middlewares/auth.js while preserving the existing middleware order. The token is already available via globals.getToken() or directly from ctx.request.headers.authorization.

Here is a production-ready implementation using jsonwebtoken:

// src/middlewares/auth.js (production implementation)
const jwt = require('jsonwebtoken');
const globals = require('@src/globals');

const excludePatterns = ['/api/agent_store/last/'];

module.exports = () => {
  return async (ctx, next) => {
    const path = ctx.path;
    
    // Skip auth for excluded paths
    if (excludePatterns.some(p => path.startsWith(p))) {
      return next();
    }

    const token = globals.getToken();
    if (!token) {
      return ctx.response.fail(null, 'Authentication required', 401);
    }

    try {
      const payload = jwt.verify(token, process.env.JWT_SECRET);
      ctx.state.user = { 
        id: payload.sub, 
        roles: payload.roles || [] 
      };
      await next();
    } catch (err) {
      return ctx.response.fail(null, 'Invalid or expired token', 401);
    }
  };
};

Downstream route handlers can then access the authenticated user:

// src/routers/user/users.js (excerpt)
router.get('/profile', async (ctx) => {
  const userId = ctx.state.user?.id;
  if (!userId) {
    return ctx.response.fail(null, 'Unauthenticated', 401);
  }

  const profile = await UserService.getProfile(userId);
  ctx.response.success(profile);
});

Summary

  • Linear Pipeline: Lemon AI registers eleven middleware layers in src/app.js, ensuring request parsing and logging occur before authentication logic.
  • Token Extraction: setGlobalTokenMiddleware extracts bearer tokens from the Authorization header and stores them in a global singleton (src/globals.js), making the token accessible throughout the request lifecycle.
  • Authentication Gate: authMiddleware (src/middlewares/auth.js) currently acts as a stub that assigns a default user object, but is structurally prepared for JWT/OAuth validation with an excludePatterns array for public routes.
  • Extensibility: The middleware chain is designed for easy replacement of the authentication stub with production-grade validation without altering the overall pipeline structure.

Frequently Asked Questions

How does Lemon AI extract and store authentication tokens?

The setGlobalTokenMiddleware in src/middlewares/setGlobalToken.js reads the Authorization header, checks for the Bearer prefix, and stores the token string in a global singleton defined in src/globals.js. This makes the token available to any downstream middleware or route handler via globals.getToken().

Where is the authentication logic located in the middleware chain?

The authentication gate is implemented in src/middlewares/auth.js and is registered after the token extraction middleware in src/app.js. This ensures the token is already available in the global store before the authentication middleware attempts validation. The current implementation is a stub that assigns a default user, but the structure supports JWT or OAuth integration.

Can I exclude specific routes from authentication?

Yes. The auth.js middleware includes an excludePatterns array that lists paths which should bypass authentication checks. While the current stub implementation does not actively filter these paths, the data structure is in place to support conditional skipping for public endpoints like /api/agent_store/last/.

How do I access the authenticated user in route handlers?

After the authentication middleware executes, the user object is attached to ctx.state.user. Route handlers can access this property to retrieve the user ID and roles. For example, const userId = ctx.state.user?.id; retrieves the identifier set during authentication, enabling user-scoped database queries and authorization checks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →