---
title: "Token Authentication for AI Agents: Lessons Learned"
description: "How we handle auth tokens in a system where both humans and AI agents make API calls — short-lived signed session tokens, request-signature service-to-service, and token propagation."
canonical: https://nowah.xyz/blog/jwt-authentication-ai-agents
lastModified: "2026-08-07T03:52:54.423Z"
---

# Token Authentication for AI Agents: Lessons Learned

How we handle auth tokens in a system where both humans and AI agents make API calls — short-lived signed session tokens, request-signature service-to-service, and token propagation.

The AI agent just booked a $2,000 flight to Tokyo. It searched live inventory, compared fares, processed a payment, and confirmed the reservation. The traveler is happy. But here's the question that should keep you up at night: how do we know the agent was authorized to do that?

Authentication in an AI-native application is different from traditional web apps. In a traditional app, a human clicks a button and the request carries their auth token. Straightforward. In our system, the traveler sends a chat message, the AI agent reasons about it, and then the agent makes a series of API calls on the traveler's behalf. The agent is acting as a proxy. It has access to powerful tools. It needs to be constrained to only what the specific traveler is authorized to do.

Getting this wrong means an agent booking flights on the wrong account, accessing another traveler's documents, or processing payments without proper authorization. That's not a hypothetical risk. It's the kind of bug that ends companies.

## Short-lived signed session tokens with automatic refresh

![Illustration for this section](https://pics.nowah.xyz/website-media/infrastructure-016-img-1.webp)

We use JSON Web Tokens for user authentication. The mobile app gets a fresh token from our identity provider before each API request. The token carries the user's identity, expiration time, and basic claims.

The critical design choice is short expiration. Our tokens live for minutes, not days. If a token gets compromised (intercepted on a network, leaked through logging, extracted from a device), the window of exposure is small. An attacker has minutes to use the token, not weeks.

Short-lived tokens require automatic refresh. The client doesn't wait for a token to expire and then show a login screen. It proactively gets a fresh token before each request. The identity provider handles the refresh cycle: exchange a refresh token for a new access token, seamlessly, without user interaction.

This pattern means the user stays logged in indefinitely (good experience) while individual tokens expire quickly (good security). The refresh token itself has tighter security controls: it's stored in secure device storage, not accessible to JavaScript, and can be revoked server-side if needed.

## Token propagation across services

When a request arrives at our API server, the auth middleware verifies the signed session tokens and extracts the user identity. From that point forward, every downstream operation knows who the user is. The user identity propagates through the request context, and every service that handles part of the request can check authorization against it.

This is important for the AI agent. When the agent calls a tool to [search flights](/blog/launching-[tool-calling](/blog/tool-calling-at-scale-ai-travel-search)-layer-ai-agent-search-flights), the tool knows which user initiated the conversation. When the agent calls a tool to create a booking, the booking is created for the authenticated user. The agent can't accidentally (or maliciously, via [prompt injection](/blog/prompt-injection-new-sql-injection)) operate on a different user's account because the user identity comes from the verified signed session tokens, not from anything the agent decides.

The propagation pattern is simple: authenticate once at the API boundary, then trust the identity downstream. We don't re-verify the signed session tokens at every internal service call. The API server verified it. Internal services trust the propagated identity. This reduces latency (no redundant crypto operations) and simplifies internal service code.

## Service-to-service auth via request-signature

![Supporting diagram](https://pics.nowah.xyz/website-media/infrastructure-016-img-2.webp)

Not every API call comes from a human. Some come from internal services. The job queue worker needs to call the API to send notifications. The booking service needs to call the payment service. These calls don't have a user signed session tokens because they're initiated by the system, not by a user action.

For service-to-service authentication, we use request signatures. The calling service signs the request with a shared secret. The receiving service verifies the signature. If the signature is valid, the request is from a trusted internal service.

request-signature is lighter than signed session tokens for this purpose. We don't need the claims structure of a signed session tokens. We just need to verify that the caller is a trusted service. A signature over the request payload with a shared secret accomplishes that with minimal overhead.

The service-to-service calls still carry user identity when applicable. If the notification worker is sending a [push notification](/blog/push-notification-travel-alerts) for a specific user's booking, the request includes the user ID as a header. The receiving service trusts this header because the request-signature signature proves the caller is an authorized internal service.

## Authenticating AI agent tool calls

Here's where it gets interesting. The AI agent makes tool calls during a conversation. Some tools are read-only (search flights, check weather). Some are state-changing (create booking, process payment). The agent needs to be authorized for each tool call, and that authorization must reflect the specific user's permissions.

Every state-changing tool call goes through an authorization check. The check verifies: is this user authenticated? Does this user have permission to perform this action? Is the target resource (trip, booking, document) owned by or shared with this user?

The agent can't bypass these checks. The tool authorization layer sits below the agent, in the middleware. The agent calls a tool, the tool implementation checks the user's permissions before executing. If the user doesn't have permission, the tool returns an error, and the agent reports it to the user.

This means a prompt injection attack that convinces the agent to "book a flight on someone else's account" fails at the authorization layer. The agent might attempt the tool call, but the authorization check rejects it because the authenticated user doesn't have access to someone else's account.

## Token revocation and session invalidation

Sometimes you need to cut off a session immediately. A user reports their phone stolen. A suspicious login pattern is detected. An abuse investigation requires blocking an account.

signed session tokens are stateless by design, which makes instant revocation tricky. The token is self-contained. The server doesn't check a database on every request to see if the token is still valid. That's what makes signed session tokens fast, but it's also what makes revocation hard.

We handle this with a revocation list in our cache layer. When a session needs to be invalidated, we add the token's unique identifier to a blocklist. The auth middleware checks this list before accepting a token. The list is in-memory cache, so the check adds negligible latency.

The blocklist entries expire when the token would have expired naturally. Since our tokens are short-lived, the entries are cleaned up quickly.

For account-level blocks (not just session invalidation), we check the user's account status as part of the auth flow. This is a lightweight database check that runs less frequently (not on every request, but periodically) to catch account suspensions.

## Auth patterns for AI-native applications

If you're building auth for an AI-native application, here's what differs from traditional web apps.

The AI agent acts on behalf of users, not as itself. Every agent action must be traceable to a specific authenticated user. Don't let the agent have its own identity that bypasses user-level authorization.

Tool calls need authorization, not just the initial request. A single user message might trigger ten tool calls. Each one that modifies state needs an authorization check. Don't assume that if the initial message was authenticated, all subsequent actions are authorized.

Service-to-service communication needs its own auth mechanism. Internal services calling each other should use a different auth pattern (like request-signature) than external clients (like signed session tokens). Mixing them creates confusion about trust boundaries.

Auth endpoints need stricter rate limits than other endpoints. We limit authentication attempts to 10 per 15 minutes. Brute force attacks on auth are a real threat, and the damage from a compromised account on a booking platform is severe.

Short token lifetimes are worth the complexity. The engineering cost of token refresh is small. The security benefit of limiting compromise windows is large. For a platform that handles payments and personal documents, short-lived tokens are not optional.

---

Nowah is an AI travel agent that searches and books real flights and hotels through conversation — no filters, no thirty open tabs. [Plan your next trip](https://app.nowah.xyz).
