Agent Authentication
An Agent application typically invokes models and tools. Without login authentication, anyone can directly access the Agent API, which may lead to the following issues:
Resource abuse: Unauthenticated users can also consume LLM and tool invocation quotas.
APIs are vulnerable to bypassing: Attackers can skip the frontend page and directly request APIs such as
/agents/*.This document uses an actual project as an example to illustrate how to build a login authentication flow based on Makers platform capabilities: Cloud Functions handles user sign-up and JWT issuance, while the platform middleware intercepts unauthenticated requests at the edge nodes in advance.
Project Key Files and Responsibilities
File / Module | Main Responsibility |
middleware.js | Platform middleware. When a protected path is accessed, it first validates the Cookie JWT. If validation fails, it directly returns a 401 status. If successful, it calls next(). |
cloud-functions/auth/* | Handles user login, sign up, logout, and current user queries. It is responsible for validating account credentials and issuing JWTs. |
agents/chat/index.ts | Agent example entry point, demonstrating authentication and streaming responses. |
db/migrations/users.sql | Neon database table schema |
Implementation Principles
Core Model
User browser││ Log In / Sign Up▼cloud-functions/auth/*│ Verify the account and password│ Write to / Query Neon│ Issue JWT▼The HttpOnly Cookie named jwt_token is saved by the browser.││ Invoke Agent▼middleware.js│ JWT from the pre-existing Cookie│ Invalid: A 401 status is returned directly.│ Valid: The result of next() is passed through.▼Agent Runtime│ Call requireAuth(context) again.│▼Return the SSE / Agent response.
JWT Content Conventions
interface JwtPayload {sub: string; // users.id,UUID v4username: string; // Usernameiat: number; // Issuance time, a timestamp in secondsexp: number; // Expiration time, a timestamp in seconds}
Implementation Process
Sign-In / Sign-Up Process
Browser││ ① POST /auth/login or /auth/register▼middleware.js││ ② If /auth/* is not in the matcher, it is passed through directly.▼cloud-functions/auth/*││ ③ Verify the username / password.│ ④ Query or write to the Neon users table.│ ⑤ Verify or generate a password_hash using bcrypt.│ ⑥ Issue a JWT using JWT_SECRET.▼Browser││ ⑦ Set-Cookie: jwt_token=...▼Login completed.
Key Points:
/auth/login and /auth/register must be public APIs.Do not include them in
middleware.config.matcher.Upon successful login, the token is saved only via HttpOnly Cookie and is not exposed to frontend JS.
Agent Invocation Process
Browser││ ⑧ POST /chat, automatically including the Cookie: jwt_token=...▼middleware.js││ ⑨ Verify JWT using Web Crypto.│ Failure: 401│ Success: next()▼Agent Runtime││ ⑩ Verify JWT again using requireAuth(context).▼Browser││ ⑪ Stream back the Agent response via SSE.
Key Points:
After the middleware successfully verifies the signature, it only forwards the original request.
The Agent reads the JWT from the Cookie and independently verifies the signature (optional).
Key Implementation of Platform Middleware
File:
middleware.jsResponsibility: When a protected path is accessed, verify the JWT from the Cookie. If verification fails, return a 401 status / if successful, call next().
Configuring Protected Paths
export const config = {matcher: ['/chat/:path*','/stop/:path*','/history/:path*','/agents/:path*',],};
Note:
matcher is the sole source for the middleware's protection scope.Do not include
/auth/* in the matcher.Static resources and frontend page routes generally do not need to be added to the matcher.
Main Logic
export async function middleware(context) {const { request, next, env } = context;const token = readCookie(request.headers, 'jwt_token');if (!token) return unauthorized('no auth cookie');try {await verifyJwt(token, env.JWT_SECRET);} catch (e) {return unauthorized(e.message || 'verify failed');}return next();}
Key Points for Signature Verification
The middleware runs in the edge V8 environment and uses Web Crypto:
const key = await crypto.subtle.importKey('raw',utf8ToBytes(secret),{ name: 'HMAC', hash: 'SHA-256' },false,['sign', 'verify'],);
During signature verification, you must check:
Check Item | Purpose |
Verify whether the token is in three-part format. | Prevent malformed tokens. |
header.alg === 'HS256' | Prevent alg=none and algorithm confusion. |
Verify whether the HMAC signature is consistent. | Confirm that the token has not been tampered with. |
Verify whether payload.exp is expired. | Prevent old tokens from remaining valid indefinitely. |
Key Implementation of Cloud Functions
Directory:
cloud-functions/Responsibilities: Sign Up / Log In / Current User Information / Log Out
File Structure
File | Feature |
auth/register/index.ts | Registers users, writes to Neon, and issues JWTs |
auth/login/index.ts | Validates passwords and issues JWTs. |
auth/user/index.ts | Determines the currently logged-in user from the Cookie. |
auth/logout/index.ts | Clears cookies. |
Key Implementation on the Agent Side
File example:
agents/chat/index.tsFirst-Step Signature Verification at the Entry Point (Optional)
The value of the middleware is early rejection: it blocks unauthenticated requests at the edge node to reduce costs for Agent Runtime, Sandbox, and LLM. However, the middleware is not the ultimate security boundary. You can perform an additional signature verification on the Agent side.
import { requireAuth, AuthError, unauthorizedResponse } from '../_jwt';export async function onRequest(context: any) {let auth;try {auth = requireAuth(context);} catch (e) {if (e instanceof AuthError) {return unauthorizedResponse(e.reason);}throw e;}// Only from this point onward can the Agent business logic be executed.}
Database Configuration and Implementation
Neon is Serverless Postgres. This solution uses it to store user tables and accesses it via
@neondatabase/serverless over HTTPS. Alternatively, you can choose other third-party databases.Configuration Steps
1. Create a project in the Neon console.
2. Obtain the connection string. The format should be similar to:
postgresql://<user>:<password>@<host>/<db>?sslmode=require
3. Configure it in the EdgeOne Makers project environment variables:
Variable | Required | Description |
DATABASE_URL | Yes | Neon connection string. It is recommended to include ?sslmode=require. |
JWT_SECRET | Yes | JWT signing key, which must be consistent across all three tiers. |
During local development, configure the variable with the same name in
.env.Database Table Structure
CREATE EXTENSION IF NOT EXISTS pgcrypto;CREATE TABLE IF NOT EXISTS users (id UUID PRIMARY KEY DEFAULT gen_random_uuid(),username VARCHAR(64) NOT NULL,password_hash VARCHAR(255) NOT NULL,created_at TIMESTAMPTZ NOT NULL DEFAULT NOW());CREATE UNIQUE INDEX IF NOT EXISTS users_username_lower_uniqON users (LOWER(username));
Note:
id is automatically generated by the Postgres gen_random_uuid() function.Only the bcrypt hash is stored in
password_hash, and plaintext passwords are not stored.The
LOWER(username) unique index is used to prevent Alice and alice from signing up simultaneously.Integration Steps
If you want to integrate this solution into your own Agent project, follow these steps in order:
1. Create a Neon database and execute
db/migrations/users.sql.2. Configure
DATABASE_URL and JWT_SECRET.3. Copy
cloud-functions/auth/*, cloud-functions/_jwt.ts, cloud-functions/_db.ts, and cloud-functions/_validate.ts.4. Configure the
matcher in middleware.js to cover all protected Agent paths.5. Add
requireAuth(context) as the first step in each Agent entry.
