# Custom channel auth

Verify Clerk callers at your eve agent's channel boundary — accept web sessions, API keys, and machine tokens, and map each to an eve principal your tools and instructions can read.

> Skip the manual setup — install the configurable `clerkAuth()` helper via shadcn.

filename: terminal
```bash
$ bunx --bun shadcn@latest add clerk/eve-agents/auth
```

***

Everything below builds the channel `AuthFn` from scratch: session verification first, then multi-token support and attribute mapping.

An eve channel authorizes inbound requests through a list of [`AuthFn` callbacks](https://eve.dev/docs/guides/auth-and-route-protection). Each entry receives the request, returns an eve principal to accept, or returns `null` to skip to the next entry.

```ts
import type { AuthFn } from 'eve/channels/auth'

const myAuth: AuthFn<Request> = async (request) => {
  // Return a SessionAuthContext to accept, or `null` to skip.
}
```

## Verify a Clerk session

Hand the `Request` to Clerk's [`authenticateRequest()`](https://clerk.com/docs/reference/backend/authenticate-request.md) and map the verified state to an eve principal.

filename: agent/channels/eve.ts
```ts
import { createClerkClient } from '@clerk/backend'
import type { AuthFn } from 'eve/channels/auth'
import { eveChannel } from 'eve/channels/eve'

const clerk = createClerkClient({
  secretKey: process.env.CLERK_SECRET_KEY,
  publishableKey: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
})

const clerkAuth: AuthFn<Request> = async (request) => {
  const state = await clerk
    .authenticateRequest(request, { acceptsToken: 'session_token' })
    .catch(() => null)

  if (!state?.isAuthenticated) return null

  const auth = state.toAuth()

  return {
    authenticator: 'clerk',
    principalType: 'user',
    principalId: auth.userId,
    subject: auth.userId,
    attributes: { tokenType: auth.tokenType },
  }
}

export default eveChannel({
  auth: [clerkAuth],
})
```

The returned object is what every downstream piece (instructions, tools, subagents) reads as `ctx.session.auth.current`.

## Reject vs. fall through

Returning `null` skips this authenticator and passes the request to the next entry in the channel's auth chain. If no entry accepts, the request is rejected. Throw [`UnauthenticatedError`](https://eve.dev/docs/guides/auth-and-route-protection) instead to short-circuit the chain with a `401` — useful when Clerk is a required authentication strategy.

```ts
import { UnauthenticatedError } from 'eve/channels/auth'

if (!state?.isAuthenticated) {
  throw new UnauthenticatedError({
    code: 'authentication_required',
    message: `Clerk auth failed (${state?.reason ?? 'unknown'})`,
  })
}
```

## Accept multiple token types

Pass an array to [`acceptsToken`](https://clerk.com/docs/reference/backend/authenticate-request.md#authenticate-request-options) to verify more than one Clerk token type in the same call. Switch on `auth.tokenType` to branch on what authenticated.

filename: agent/channels/eve.ts
```ts
const clerkAuth: AuthFn<Request> = async (request) => {
  const state = await clerk
    .authenticateRequest(request, {
      acceptsToken: ['session_token', 'api_key', 'm2m_token', 'oauth_token'],
      machineSecretKey: process.env.CLERK_MACHINE_SECRET_KEY,
    })
    .catch(() => null)

  if (!state?.isAuthenticated) return null

  const auth = state.toAuth()

  if (auth.tokenType === 'session_token') {
    return {
      authenticator: 'clerk',
      principalType: 'user',
      principalId: auth.userId,
      subject: auth.userId,
      attributes: { tokenType: auth.tokenType },
    }
  }

  // API key, M2M, OAuth — Clerk groups these as machine principals.
  return {
    authenticator: 'clerk',
    principalType: 'machine',
    principalId: auth.subject,
    subject: auth.subject,
    attributes: { tokenType: auth.tokenType },
  }
}
```

`machineSecretKey` is required when accepting `m2m_token`. Clerk uses it to verify the inbound token. See [API keys & M2M](https://clerk.com/docs/guides/ai/eve/api-keys-and-m2m.md) for setup.

## Add attributes

[Attributes](https://eve.dev/docs/guides/auth-and-route-protection) is a free-form object exposed as shared auth context in instructions, tools, and subagents. Customize it by pulling fields off the Clerk auth object.

Session token:

```ts
const attributes: Record<string, string | readonly string[]> = {
  tokenType: auth.tokenType,
}

if (auth.orgId) attributes.orgId = auth.orgId
if (auth.orgRole) attributes.role = auth.orgRole
if (auth.orgPermissions?.length) attributes.permissions = auth.orgPermissions
```

You can pull anything off `auth.sessionClaims` to enrich attributes too — useful for [custom claims](https://clerk.com/docs/guides/sessions/customize-session-tokens.md) you've added to the session token.

```ts
const name = auth.sessionClaims?.name
if (typeof name === 'string') attributes.name = name
```

Machine variants all share `scopes`, with additional fields specific to each token type:

```ts
const attributes: Record<string, string | readonly string[]> = {
  tokenType: auth.tokenType,
}
if (auth.scopes?.length) attributes.scopes = auth.scopes

if (auth.tokenType === 'api_key') {
  // API keys are scoped to either a user or an org, never both.
  if (auth.userId) attributes.userId = auth.userId
  if (auth.orgId) attributes.orgId = auth.orgId
}

if (auth.tokenType === 'oauth_token') {
  attributes.userId = auth.userId
  attributes.clientId = auth.clientId
}
```

Summary by token type:

| Token type      | `principalType` | Typical attributes                     |
| --------------- | --------------- | -------------------------------------- |
| `session_token` | `user`          | `orgId`, `role`, `permissions`, `name` |
| `api_key`       | `machine`       | `scopes`, `userId` _or_ `orgId`        |
| `m2m_token`     | `machine`       | `scopes`                               |
| `oauth_token`   | `machine`       | `scopes`, `userId`, `clientId`         |

See [Enrich instructions](https://clerk.com/docs/guides/ai/eve/enriching-instructions.md) and [Authorize tool calls](https://clerk.com/docs/guides/ai/eve/authorize-tool-calls.md) for how to read these downstream.

## Compose with other authenticators

Add other entries to the `auth` chain. Each runs in order; the first non-null wins.

```ts
import { vercelOidc } from 'eve/channels/auth'

export default eveChannel({
  auth: [clerkAuth, vercelOidc()],
})
```

> Omitting [`localDev()`](https://eve.dev/docs/guides/auth-and-route-protection#localdev) means unauthenticated requests get a real `401` on localhost. Useful for testing real auth flows. Remember the agent won't be reachable from your browser without signing in.

## Related guides

- [API keys & M2M](https://clerk.com/docs/guides/ai/eve/api-keys-and-m2m.md) — API key auth and agent-to-agent M2M.
- [Enrich instructions](https://clerk.com/docs/guides/ai/eve/enriching-instructions.md) — read the principal inside dynamic instructions.
- [Authorize tool calls](https://clerk.com/docs/guides/ai/eve/authorize-tool-calls.md) — gate tool invocations against permissions on the principal.

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
