You can ship a secure AI agent in hours, not weeks. If you already love building fast with Nhost, you can add OAuth2-style flows and JWT authentication without spinning up custom auth servers or wrestling with DevOps. This step-by-step guide shows exactly how to wire Nhost Authentication, GraphQL permissions, Storage, and Serverless Functions so your agents run with least-privilege access and production-grade safety.

What we’re building and why

We are setting up OAuth2 for AI agents with JWT authentication to protect your data, tools, and users. The goal: give each agent a scoped, short-lived token that only permits what it must do—nothing more. With Nhost, tokens are issued by Auth, enforced by GraphQL and Postgres policies, and honored by Storage and Serverless Functions. You get AI agent security that scales without custom infrastructure.

Architecture at a glance (Nhost-first)

Computer screen displaying code and text
Photo by Bernd 📷 Dittrich on Unsplash

Here is the minimal, production-ready flow aligned to Nhost services:

  • Nhost Auth issues JWTs with roles and custom claims. These tokens are the core of LLM app auth for both user-delegated and machine-to-machine agents.
  • GraphQL API (Hasura) enforces role-based permissions and uses JWT session variables for row-level filters.
  • Storage validates tokens, applies bucket/object policies, and serves files via signed URLs.
  • Serverless Functions act as a token broker and “tooling hub” that verifies JWTs, checks scopes, and performs side effects safely.
  • Postgres stores agent state, ownership, scopes, and audit logs for transparency and rollback.

Human-delegated vs machine-to-machine

There are two common patterns when implementing OAuth2 for AI agents:

  • User-delegated (human-in-the-loop): An authenticated user grants specific scopes to an agent that acts on their behalf—think “read my docs” but not “delete my docs.” This uses a user session as the basis for agent-scoped JWT authentication.
  • Machine-to-machine (headless): A background agent obtains a scoped token via client credentials to run scheduled tasks or process webhooks without a human present. It still abides by strict roles and policies.

Both patterns benefit from least privilege, short-lived tokens, and a clean audit trail. You will use the same Nhost primitives for both flows.

Prerequisites

Before you start this step-by-step guide, set up:

  • A running Nhost project (Postgres, GraphQL, Auth, Storage, Functions are available out of the box)
  • Any required OAuth providers (Google, GitHub, Apple) enabled in Nhost Auth for human delegation
  • Your project’s app URL, subdomain, and region to configure SDKs and function endpoints

Step 1 — Configure OAuth2 providers in Nhost Auth

Enable the providers you need in Nhost Auth so users can sign in and approve agent access. Map provider profiles to a single user record per email to avoid duplicates. Make sure refresh tokens are enabled to support re-consent and long-lived user sessions without constant re-logins.

Create an “agent” role

Add a dedicated agent role in Auth. Assign it only the permissions required for AI agent security, never service-level superpowers. Users who own agents can keep their default roles (e.g., user), while agents that act on their behalf get the constrained agent role when exchanging grants for tokens.

Add custom JWT claims

In Nhost Auth, use user metadata to enrich tokens with claims. Include:

  • x-hasura-allowed-roles: e.g., [user, agent]
  • x-hasura-default-role: e.g., agent for agent-scoped tokens
  • x-hasura-user-id: the owner or principal behind the session
  • x-hasura-agent-id: a unique ID for the agent instance (if applicable)

These claims drive GraphQL permissions and Postgres row-level security. They also simplify LLM app auth across external tools and functions that need to trust the same identity story.

Step 2 — Model agents and scopes in Postgres

Create tables in Postgres to represent agents, who owns them, which scopes they hold, and when/what they changed:

  • ai_agents: stores agent IDs, owner user IDs (tied to auth.users), current status, and timestamps
  • ai_agent_scopes: references ai_agents and enumerates granular permissions like docs:read, docs:write, emails:send, or vectors:ingest
  • audit tables: optional event log of actions taken by each agent (method, target, result, time)

RLS policies for agents

Enable row-level security (RLS) so the agent role can only see and modify rows linked to its identity. Use conditions based on x-hasura-user-id and x-hasura-agent-id so agents cannot cross tenant boundaries. This provides strong data isolation and supports multi-tenant AI agent security by default.

Step 3 — Build a token broker with Serverless Functions

Use Nhost Serverless Functions to host a lightweight token broker—your OAuth2-style token endpoint. It validates incoming grants, looks up allowed scopes in Postgres, and issues short-lived JWTs that default to the agent role. Keep tokens focused:

  • Short expiry for reduced blast radius
  • Only necessary claims (roles, user/agent IDs, org/team IDs if you use them)
  • Minimal scopes enforced downstream by GraphQL and Storage policies

Delegated flow (user → agent)

For user-delegated actions, the broker should accept a request from a logged-in user session, check that the requested scopes exist in ai_agent_scopes, and then mint an agent-scoped JWT. The token inherits x-hasura-user-id from the user while setting x-hasura-default-role to agent plus a unique x-hasura-agent-id. If scopes do not match, reject with a clear error so the user can re-consent or adjust settings.

Machine-to-machine flow

For headless agents, use client_id/client_secret stored in Nhost environment variables. The broker validates the credentials, fetches the agent record and allowed scopes, and returns an agent-role JWT. This mirrors a client-credentials flow but without exposing admin credentials or service-level secrets to the agent.

Step 4 — Lock down GraphQL with role/claim-based rules

Configure Hasura role permissions so the agent role can only run whitelisted operations. Guard every read and write with filters that reference JWT session variables. Examples include:

  • Select: allow only rows where owner_id = x-hasura-user-id
  • Insert/Update/Delete: constrain to IDs and fields explicitly allowed by the agent’s scopes
  • Function calls: expose only resolver operations that are safe for the agent role

Session variables in resolvers

Standardize on x-hasura-user-id and x-hasura-agent-id for permissions. This ensures consistent authorization checks at both GraphQL and Postgres layers. With JWT authentication in place, your LLM app auth becomes predictable, auditable, and easy to reason about.

Step 5 — Secure Storage for RAG/data tools

When agents need to read or write files (for example, RAG embeddings or user uploads), use private buckets and object-level rules that evaluate JWT claims. Serve files via signed URLs with short expiries so access is temporary and scoped.

  • Restrict buckets to the agent role where appropriate
  • Prefix objects with tenant or agent identifiers for clean, claim-based checks
  • Issue signed URLs only after validating scopes and ownership against Postgres

Content moderation and size caps

Enforce file type and size limits in a Serverless Function before generating signed URLs. Reject disallowed content early. This protects your Storage, downstream tools, and vector pipelines from abuse while maintaining tight AI agent security.

Step 6 — Verify JWTs in Functions and external tools

robot and human hands reaching toward ai text
Photo by Igor Omilaev on Unsplash

Every Serverless Function that performs side effects should verify the Authorization header, confirm role and scopes, and forward only the required claims to downstream services. If you integrate third-party tools, pass a limited, short-lived signed token or a signed URL—never long-lived secrets.

Protect tool/webhook endpoints

Require agent-role JWTs on endpoints that can send email, call scraping utilities, trigger vector writes, or schedule jobs. Do not accept the service_role on public or agent-facing routes. This keeps LLM app auth strict and auditable, and prevents accidental privilege escalation.

Step 7 — Rotate keys, revoke sessions, and audit

Security is a process. Keep your setup healthy with ongoing hygiene:

  • Short-lived tokens: expire frequently; rely on refresh or re-grant flows as needed
  • Key rotation: update signing keys and JWKS in Nhost settings on a regular schedule
  • Comprehensive audits: log each agent action to Postgres (what, when, who, result)

Revocation paths

Be ready to pull the plug fast:

  • Revoke user and agent sessions in Nhost Auth
  • Disable compromised client_id/client_secret in environment variables
  • Invalidate existing signed URLs and cut Storage access
  • Use GraphQL permission updates to instantly block risky operations

Step 8 — Test end-to-end

Validate the full chain with a realistic workflow. Obtain a delegated or machine token from your token broker, confirm the token’s claims, then run a restricted GraphQL query that should succeed (e.g., select rows owned by the user). Try a query that should fail and expect a 403. Next, request a Storage signed URL for a file within the agent’s scope and confirm it downloads, then test another object outside the scope and verify access is denied.

Common error handling

Design your agent logic to gracefully handle:

  • 401 Unauthorized: refresh the session or start a new grant flow
  • 403 Forbidden: surface which scope or role check failed in logs so you can adjust policies or requests
  • Expired signed URLs: request a new link after re-validating ownership and scope

Deploy with Git/CI and Bring Your Own Containers

Push everything to production with Nhost Git deployments. Commit your database schema, Hasura metadata, and Serverless Functions to version control. On each push, Nhost builds and deploys your changes with repeatable, environment-aware steps. If you need a custom OAuth2 provider, SAML bridge, or advanced broker logic in another language, use Bring your own containers to run sidecar services alongside your Nhost stack—still benefiting from the same GraphQL, Postgres, and Storage foundation.

Secrets management

Store provider secrets, client_id/client_secret, and API keys in Nhost environment variables. Never embed the service_role or admin credentials in agents, browser code, or public endpoints. Restrict secrets by environment (dev, staging, prod) and rotate regularly.

Hardening tips for production

  • Minimize scopes: start from zero and add only what an agent truly needs
  • Prefer allowlists: whitelist queries, mutations, and buckets for the agent role
  • Rate limit: add limits to token broker endpoints and side-effecting functions
  • Monitor anomalies: watch for unusual rates, large file patterns, or repeated 403s
  • Isolate noisy tools: run risky operations in separate functions or containers with tighter controls

With this setup, you get the speed of a managed backend and the rigor of production security. Your AI agents receive just enough power to do their job—nothing more. OAuth2 for AI agents and JWT authentication are first-class citizens on Nhost, which means you are free to iterate on features instead of wrestling with infrastructure.

Conclusion

Shipping secure, least-privilege agents does not require heavy custom servers or months of work. Using Nhost Authentication for tokens, GraphQL permissions for enforcement, Postgres for state and audit, Storage for controlled file access, and Serverless Functions for brokering and tools, you have everything needed to implement robust AI agent security. This step-by-step guide gets you from zero to production patterns quickly—so you can move fast without breaking trust.

Ready to put this into practice and launch agents safely? Get started for free.

Frequently Asked Questions

How do I add custom JWT claims (like organization_id or agent_id) to Nhost tokens?

Use Nhost Auth’s user metadata to map additional claims into the JWT. Alongside x-hasura-allowed-roles and x-hasura-default-role, include fields like x-hasura-organization-id or x-hasura-agent-id. These claims become session variables that GraphQL and Postgres can enforce for row-level and tenant-level isolation.

What’s the safest way to let an external LLM vendor call my Nhost GraphQL API?

Issue short-lived, agent-role JWTs via a Serverless Function acting as a token broker. Limit scopes, enforce row-level filters based on claims, and only expose whitelisted queries and mutations. For files, use signed URLs with brief expiries. Avoid sharing long-lived secrets or service_role tokens with vendors.

Should I ever give an AI agent the service_role or admin secret?

No. Do not grant service_role or admin credentials to agents or public endpoints. Use OAuth2 for AI agents with narrowly scoped, short-lived JWT authentication. Enforce permissions with role-based GraphQL rules, Storage policies, and Postgres RLS to maintain strict least privilege.

How do I revoke a compromised agent quickly across GraphQL, Storage, and Functions?

Immediately revoke the session in Nhost Auth, disable the agent’s client_id/client_secret in environment variables, and invalidate any active signed URLs. Optionally tighten GraphQL permissions or disable the agent record in Postgres. This combination cuts off token issuance, data access, and file retrieval paths at once.

Can I implement a client-credentials OAuth2 flow on Nhost without running my own auth server?

Yes. Create a Serverless Function as your token endpoint, store client_id/client_secret in Nhost environment variables, validate the request against Postgres records, then mint short-lived agent-role JWTs. This provides a clean client-credentials-like flow fully hosted on Nhost—no separate auth infrastructure required.

💬 Contact Us

Have a question or want to learn more? Send us a message!

500