# How to Secure Custom API Routes in Agent-Native Using getSession and Access Control

> Secure Agent-Native custom API routes! Learn to use getSession and access control for robust authentication and authorization. Protect your sensitive data and logic effectively.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: how-to-guide
- Published: 2026-07-02

---

**Secure custom API routes in Agent-Native by retrieving the session with `getSession(event)`, validating authentication credentials, and enforcing role-based or resource-level permissions before executing sensitive business logic.**

Agent-Native, the open-source framework maintained by BuilderIO, builds server-side endpoints on top of the Nitro framework. Custom API routes reside in `templates/*/server/routes/` and receive requests as H3 events, requiring explicit authentication checks using the core session management utilities found in [`packages/core/src/server/auth.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/server/auth.ts).

## Retrieving the Current Session

Every protected route must extract the caller's identity using the `getSession` utility. This function inspects incoming request headers for cookies, Bearer tokens, or MCP OAuth credentials and returns a standardized session object containing the user's `email`, `id`, `role`, and custom claims.

The implementation resides in the core package at [`packages/core/src/server/auth.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/server/auth.ts). Import it directly into your route handler:

```typescript
import { getSession } from "@agent-native/core/server";
import { createError } from "h3";

export default async (event) => {
  const session = await getSession(event).catch(() => null);
  
  if (!session) {
    throw createError({ statusCode: 401, message: "Unauthenticated" });
  }
  
  return { user: session.email };
};

```

**Short-circuit unauthenticated calls immediately** after the session check to prevent unauthorized access to database queries or external API calls.

## Enforcing Role-Based and Resource-Level Access Control

Once authenticated, apply fine-grained permissions by inspecting the session payload. Common patterns include verifying email domains, checking role arrays, or confirming resource ownership.

In [`templates/plan/server/plan-asset-route.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/plan/server/plan-asset-route.ts), the implementation demonstrates owner verification:

```typescript
// templates/plan/server/plan-asset-route.ts
import { getSession } from "@agent-native/core/server";
import { createError } from "h3";
import { db, plans, eq } from "@/db";

export default async (event) => {
  const session = await getSession(event).catch(() => null);
  if (!session) {
    throw createError({ statusCode: 401, message: "Unauthenticated" });
  }

  const planId = getQuery(event).planId;
  const plan = await db.select().from(plans).where(eq(plans.id, planId));
  
  // Resource ownership check
  if (plan.ownerId !== session.id) {
    throw createError({ statusCode: 403, message: "Forbidden" });
  }

  return await getPlanAsset(planId);
};

```

For multi-tenant scenarios, scope queries using the session's **workspace id** to ensure users only access data within their organization.

## Propagating Security Context with runWithRequestContext

Privileged operations often trigger downstream actions that require the same authentication context. Wrap your handler logic inside `runWithRequestContext` to propagate the session to all `@agent-native/core/*` helpers, including `runAction` and database queries.

This pattern appears in administrative endpoints that invoke sensitive actions like `seedKitchenSink`:

```typescript
import { getSession, runWithRequestContext } from "@agent-native/core/server";
import { createError } from "h3";
import { seedKitchenSink } from "@/actions/seed-kitchen-sink";

export default async (event) => {
  const session = await getSession(event).catch(() => null);
  if (!session) throw createError({ statusCode: 401, message: "Unauthenticated" });

  // Role-based gate
  if (!session.roles?.includes("admin")) {
    throw createError({ statusCode: 403, message: "Admin role required" });
  }

  // Context propagates to all child actions
  const result = await runWithRequestContext(event, async () => {
    return await seedKitchenSink({ userId: session.id });
  });

  return result;
};

```

## Creating Reusable Authorization Helpers

Maintain clean route handlers by extracting permission logic into reusable utility functions. These helpers centralize your access control rules and improve testability.

```typescript
// src/server/utils/auth-helpers.ts
import { createError } from "h3";

export function requireAdmin(session: Session) {
  if (!session.roles?.includes("admin")) {
    throw createError({ statusCode: 403, message: "Admin role required" });
  }
}

export function requireOwner(session: Session, ownerId: string) {
  if (session.id !== ownerId) {
    throw createError({ statusCode: 403, message: "You do not own this resource" });
  }
}

```

Usage inside a route remains concise:

```typescript
import { getSession } from "@agent-native/core/server";
import { requireAdmin } from "@/server/utils/auth-helpers";

export default async (event) => {
  const session = await getSession(event).catch(() => null);
  if (!session) throw createError({ statusCode: 401, message: "Unauthenticated" });

  requireAdmin(session);
  
  // Proceed with privileged logic
  return await getSensitiveData();
};

```

## Summary

- **`getSession(event)`** in [`packages/core/src/server/auth.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/server/auth.ts) extracts user identity from multiple authentication mechanisms (cookies, Bearer tokens, MCP OAuth).
- **Early rejection** of unauthenticated requests using `createError` prevents unauthorized access to business logic.
- **Resource-level checks** compare `session.id` against resource `ownerId` or verify `session.roles` arrays for administrative functions.
- **`runWithRequestContext`** ensures downstream actions inherit the same security context, maintaining consistent permissions across the call stack.
- **Helper utilities** like `requireAdmin` and `requireOwner` keep route handlers clean and authorization logic centralized.

## Frequently Asked Questions

### How does getSession handle different authentication methods?

The `getSession` function implemented in [`packages/core/src/server/auth.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/server/auth.ts) normalizes multiple authentication sources. It inspects cookies for session tokens, Authorization headers for Bearer tokens, and MCP OAuth credentials, returning a consistent session object regardless of the authentication mechanism used by the client.

### What is the difference between 401 and 403 errors in this context?

Return **401 Unauthorized** when `getSession` returns null or fails to validate credentials, indicating the request lacks authentication entirely. Return **403 Forbidden** when the session exists but the user lacks the required role or does not own the requested resource, indicating insufficient permissions despite valid credentials.

### Can I use middleware to protect multiple routes at once?

While the examples show inline checks for clarity, you can extract the authentication pattern into a Nitro middleware function that wraps route handlers. The middleware would call `getSession`, verify the session exists, and optionally attach the session object to the event context before calling the main handler, though explicit per-route checks remain the recommended pattern for visibility.

### How do I test protected routes during development?

Create test users with specific roles in your development environment, then generate valid session tokens or cookies for those users. Pass these credentials in your test requests' headers or cookies when calling the API route. The `getSession` function will validate them against your development authentication provider just as it does in production.