JWT Authentication in Lemon AI: Structure, Content, and Implementation

Lemon AI uses a lightweight JSON Web Token (JWT) implementation with a standard HS256 signature, where the payload contains only the caller-supplied data plus an issued-at timestamp, without built-in expiration or issuer claims.

The authentication system in Lemon AI relies on a minimal JWT implementation located in src/utils/jwt.js. This utility provides the core signing and verification logic used throughout the application to secure routes and identify users. Understanding the structure and content of these tokens is essential for developers integrating with or extending the Lemon AI codebase.

Structure of the JWT in Lemon AI

The JWT implementation in Lemon AI follows the standard three-part Base64URL format (header.payload.signature). Each component is intentionally lightweight to minimize token size while maintaining security.

The header is generated by the underlying jsonwebtoken library with default values:

{
  "alg": "HS256",
  "typ": "JWT"
}

This specifies HMAC-SHA256 as the signing algorithm and identifies the token type as JWT.

Payload

The payload content is dynamically determined by the info object passed to encodeToken(info). The utility does not inject custom claims such as exp (expiration), aud (audience), or iss (issuer). The only automatically injected field is iat (issued-at timestamp).

A typical payload after encoding appears as:

{
  "userId": "12345",
  "email": "alice@example.com",
  "role": "admin",
  "iat": 1704067200
}

Signature

The signature is generated using HMAC-SHA256 with a secret key. The implementation reads the JWT_SECRET environment variable; if this variable is undefined, it falls back to the literal string 'local'. This signature ensures the token has not been tampered with since issuance.

Core Implementation Files

The JWT lifecycle is managed across several key files in the repository:

  • src/utils/jwt.js: Contains encodeToken(info) for signing payloads and decodeToken(token) for verification. The decodeToken function returns the decoded payload (including iat) on success, or null if verification fails.
  • src/middlewares/setGlobalToken.js: Middleware that extracts the Authorization header, validates the Bearer token format, and stores the raw token for downstream use.
  • src/routers/*/*.js: Route handlers (such as src/routers/conversation/conversation.js) that call decodeToken to authenticate requests before processing.
  • .env.example: Documents the required JWT_SECRET environment variable for production deployments.

Code Examples

Generating a Token

To create a JWT for a user, pass a plain object containing the necessary identification data to encodeToken:

const { encodeToken } = require('@src/utils/jwt');

const payload = {
  userId: '12345',
  email: 'alice@example.com',
  role: 'admin'
};

const token = encodeToken(payload);
console.log('JWT:', token); // Outputs: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Verifying and Decoding a Token

When processing incoming requests, extract the token from the Authorization header and verify it:

const { decodeToken } = require('@src/utils/jwt');

const tokenFromHeader = ctx.headers.authorization?.split(' ')[1]; // "Bearer <token>"

const userInfo = decodeToken(tokenFromHeader);

if (userInfo) {
  console.log('Authenticated user:', userInfo);
  // Proceed with protected operation
} else {
  console.warn('Invalid or missing JWT');
  ctx.status = 401;
  ctx.body = { error: 'Unauthenticated' };
}

Using in a Koa Route Handler

The following example demonstrates complete token validation within a route definition:

router.get('/protected', async ctx => {
  const authHeader = ctx.headers.authorization;
  const tokenString = authHeader && authHeader.startsWith('Bearer ') 
    ? authHeader.slice(7) 
    : authHeader;
    
  const user = decodeToken(tokenString);

  if (!user) {
    ctx.status = 401;
    ctx.body = { error: 'Unauthenticated' };
    return;
  }

  ctx.body = { message: 'Welcome', user };
});

Summary

  • Structure: Lemon AI JWTs follow the standard header.payload.signature format with HS256 signing.
  • Content: The payload contains only the caller-supplied data object plus an iat timestamp, without expiration or issuer claims.
  • Implementation: Core logic resides in src/utils/jwt.js, using encodeToken() for signing and decodeToken() for verification.
  • Security: Tokens are signed with the JWT_SECRET environment variable (defaulting to 'local' in development).
  • Usage: Middleware in src/middlewares/setGlobalToken.js extracts Bearer tokens from the Authorization header for route protection.

Frequently Asked Questions

How is the JWT secret configured in Lemon AI?

The JWT signing secret is read from the JWT_SECRET environment variable. If this variable is not set, the code falls back to the literal string 'local'. In production deployments, you must define JWT_SECRET in your environment or .env file to ensure token security across server restarts.

Does Lemon AI JWT implementation include automatic expiration?

No, the implementation in src/utils/jwt.js does not add an exp (expiration) claim to the payload. The encodeToken() function only injects an iat (issued-at) timestamp. If your application requires token expiration, you must either add an exp field to the payload object before calling encodeToken(), or implement expiration checks in your middleware after decoding.

What algorithm does Lemon AI use for JWT signing?

Lemon AI uses the HS256 (HMAC-SHA256) algorithm. This is specified in the JWT header as "alg": "HS256" and is the default algorithm used by the jsonwebtoken library when signing with a symmetric secret key. The signature is generated using the HMAC-SHA256 hash of the base64url-encoded header and payload, signed with the secret key.

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 →