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

> Explore JWT authentication in Lemon AI. Discover its HS256 structure, payload content, and how calls are secured without built-in expiration or issuer claims.

- Repository: [hexdocom/lemonai](https://github.com/hexdocom/lemonai)
- Tags: how-to-guide
- Published: 2026-03-03

---

**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`](https://github.com/hexdocom/lemonai/blob/main/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.

### Header

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

```json
{
  "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:

```json
{
  "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`](https://github.com/hexdocom/lemonai/blob/main/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`](https://github.com/hexdocom/lemonai/blob/main/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`](https://github.com/hexdocom/lemonai/blob/main/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`:

```javascript
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:

```javascript
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:

```javascript
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`](https://github.com/hexdocom/lemonai/blob/main/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`](https://github.com/hexdocom/lemonai/blob/main/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`](https://github.com/hexdocom/lemonai/blob/main/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.