---
title: Build user-aware TanStack Start apps with Vercel Passport
description: Use `getIdentity()` to read a visitor’s verified Vercel Passport identity in TanStack Start server functions, then personalize internal apps, gate routes by group, and log actions.
url: "https://vercel.com/kb/guide/vercel-passport-tanstack-start"
published: 2026-10-14
last_updated: 2026-10-14
authors: Ben Sabic
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Vercel Passport signs visitors in with your own identity provider, such as Okta or Microsoft Entra ID, before a request reaches your TanStack Start app. Once a visitor is through, the `@vercel/passport` SDK gives your server functions and server routes a verified identity with a stable visitor ID. Use it to personalize pages, restrict sensitive routes, and record who did what, without building your own auth flow.

In this guide, you'll learn how to:

- Install `@vercel/passport` and read the visitor identity with `getIdentity()`
  
- Choose a stable identifier for each visitor
  
- Develop locally with a Passport development identity
  
- Put Passport identity to work with server function middleware, route context, and layout routes
  

## Before you begin

Make sure you have:

- A Vercel team on the Enterprise plan, which is required for Passport.
  
- A TanStack Start (React) project deployed to Vercel with the [Nitro Vite plugin](https://vercel.com/docs/frameworks/full-stack/tanstack-start).
  
- Passport enabled on the project with an identity provider configured. If you haven't set this up yet, follow [Set up Passport with an identity provider](https://vercel.com/docs/passport/set-up-identity-provider).
  

## How Vercel Passport identity reaches your TanStack Start app

Vercel Passport runs in Vercel's network before your deployment's routes, server functions, and TanStack Start middleware execute. When a visitor without a session opens a protected deployment, Vercel redirects them to your identity provider. After the provider authenticates them, Vercel validates the response, stores a signed session token in a HttpOnly cookie, and forwards the same token to your deployment in the `x-vercel-oidc-passport-token` request header.

The token is a Vercel-signed JSON Web Token (JWT) that combines standard OpenID Connect claims, deployment context, and identity claims from your provider. Vercel strips any client-supplied value for `x-vercel-oidc-passport-token` and injects its own verified token, so a visitor can't forge an identity by setting the header themselves.

Passport controls who can open your deployment. To control what each visitor can see and do inside it, read their verified identity in your server functions and server routes.

## Install the Vercel Passport SDK

Install `@vercel/passport` in your TanStack Start project:

**pnpm**

```bash
pnpm i @vercel/passport
```

**npm**

```bash
npm i @vercel/passport
```

**yarn**

```bash
yarn add @vercel/passport
```

**bun**

```bash
bun add @vercel/passport
```

## Read the visitor identity with `getIdentity()`

The `getIdentity()` function from `@vercel/passport` reads the Passport token from the request, verifies it, and returns an identity object. It returns `null` when the request has no Passport identity, for example a request that used [Protection Bypass for Automation](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation) without an existing Passport session.

Create a shared helper in `src/lib/passport.server.ts` so every server function and server route reads identity the same way:

```typescript
import { getRequestHeaders } from '@tanstack/react-start/server';
import { getIdentity } from '@vercel/passport';

export type PassportIdentity = NonNullable<
  Awaited<ReturnType<typeof getIdentity>>
>;

// Returns the verified Passport visitor, or null if there isn't one.
export async function getVisitor(): Promise<PassportIdentity | null> {
  try {
    // Pass the incoming request headers so getIdentity() reads this request.
    return await getIdentity({ headers: getRequestHeaders() });
  } catch {
    // Treat verification failures as unauthenticated. Don't log the raw token.
    return null;
  }
}

// Stable key for storing a visitor. Combines issuer and subject.
export function visitorKey(identity: PassportIdentity): string {
  return `${identity.payload.iss}|${identity.subject}`;
}
```

The helper passes the current request's headers from `getRequestHeaders()` to `getIdentity()` explicitly. This keeps verification working whether or not the runtime exposes Vercel's request context, and it ties each identity lookup to the request that the server function is handling.

The `.server.ts` suffix marks the file as server-only, so TanStack Start keeps it out of the client bundle. Only call `getVisitor()` from server function middleware, a server function handler, or a server route handler, where `getRequestHeaders()` has a request to read.

TanStack Start renders routes and runs server functions on every request by default, so identity is read per request. Don't prerender identity-dependent routes or send public `Cache-Control` headers from them. The prerendered route renders at build time, when no identity exists, and a cached response can serve one visitor's data to another by accident.

The identity object exposes these fields:

| Field             | Type             | Description                                                                                                                  |
| ----------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `subject`         | string           | Stable Passport principal (the `sub` claim). Use this as the visitor ID. Treat it as opaque and don't parse it.              |
| `externalSubject` | string           | Subject from your identity provider (the `external_sub` claim). Specific to the Connect application and not globally unique. |
| `email`           | string, optional | Email returned by your identity provider, if any.                                                                            |
| `name`            | string, optional | Display name returned by your identity provider.                                                                             |
| `token`           | string or null   | The raw Passport JWT, or null for the local development identity. Use it only to forward identity to a backend you operate.  |
| `payload`         | object           | All verified token claims, including `iss`, `aud`, `exp`, and any allowlisted claims such as `groups`.                       |

## Which claim to use as the visitor identifier

Use `identity.subject` (the `sub` claim) as the visitor identifier when your app accepts tokens from one Passport issuer. If your app accepts tokens from more than one Vercel team, store `payload.iss` and `subject` together, which is what the `visitorKey()` helper above does.

Don't use `email`, `name`, `sid`, or `external_sub` alone as a global identifier. These values can change, be absent, or overlap across identity providers and Connect applications. Visitors who sign in through a different Connect application also get a different Passport subject.

## How to develop locally with a Passport development identity

The local `vite dev` server never receives the real Passport cookie or header, because Passport only runs in Vercel's network. When neither is present, `getIdentity()` returns a development identity and logs a warning the first time:

```plaintext
subject: owner:local:connector:local:principal:test-user
externalSubject: test-user
email: test-user@passport.local
name: Test User
```

The development identity has `verified: false` and no `token`, so treat it as a fixture for your application logic, not as proof the sign-in flow works.

Preview and production deployments never return a development identity. Any process where `VERCEL_ENV` is unset does, which includes a production build you run on your own machine.

To match the values your app expects, set them in `.env.local`, which TanStack Start loads during local development:

```bash
VERCEL_PASSPORT_DEV_OWNER=acme
VERCEL_PASSPORT_DEV_OWNER_ID=team_your_team_id_here
VERCEL_PASSPORT_DEV_PROJECT=my-project
VERCEL_PASSPORT_DEV_PROJECT_ID=prj_your_project_id_here
VERCEL_PASSPORT_DEV_CONNECTOR_ID=scl_dev
VERCEL_PASSPORT_DEV_EXTERNAL_SUB=user_dev
VERCEL_PASSPORT_DEV_EXTERNAL_ISS=https://idp.example.com
```

To test how your app handles a missing identity, set `VERCEL_PASSPORT_DEV=0` in `.env.local`, which makes `getIdentity()` return `null`. To test the real redirect, session cookie, and verified claims, use a Passport-protected preview deployment.

## Common patterns for Passport identity in TanStack Start

Once `getVisitor()` returns a verified identity, TanStack Start gives you three places to put it to work: server function middleware, route context, and layout routes. The following use cases build on these primitives, plus one for calling other services:

- Protect server functions with Passport middleware: Read the identity once and pass a typed visitor to every server function that needs it, including the actor for audit records.
  
- Share the visitor with every route: Load the visitor once in the root route and read it from route context anywhere in the app.
  
- Lock down a section of your app: Gate every route under a pathless layout route, and every server function those routes call, by identity provider group.
  
- Forward identity: Pass the verified visitor to another backend you operate and verify it there.
  

### Protect server functions with Passport middleware

Server function middleware lets you read the Passport identity once and hand a verified visitor to every server function that needs it. Without middleware, each handler has to call `getVisitor()` and check the result itself, and one forgotten check leaves a server function open.

Create `src/lib/passport.middleware.ts` with a middleware that requires a Passport visitor and passes it to the server function:

```typescript
import { createMiddleware } from '@tanstack/react-start';
import { getVisitor } from './passport.server';

// Requires a Passport visitor and adds it to the server function's context.
export const passportMiddleware = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    const visitor = await getVisitor();

    if (!visitor) {
      throw new Error('Unauthorized');
    }

    return next({ context: { visitor } });
  },
);
```

Attach the middleware to a server function with `.middleware()`. The handler then receives `context.visitor` as a verified identity. TanStack Start types the context from the attached middleware, so TypeScript reports an error if a handler reads `context.visitor` without `passportMiddleware`.

#### Attribute actions to the signed-in visitor

When several people share an internal tool, you usually want to record who created a request or approved a change. With `passportMiddleware` attached, every server function already has a verified actor, so it can stamp each write with the visitor's identity.

Store `visitorKey(context.visitor)` as the actor and treat `email` as a display value, because email addresses can change or be absent.

Create `src/lib/requests.functions.ts` with the middleware, a validator for the input, and a handler that records the actor:

```typescript
import { createServerFn } from '@tanstack/react-start';
import { passportMiddleware } from './passport.middleware';
import { visitorKey } from './passport.server';

export const submitRequest = createServerFn({ method: 'POST' })
  .middleware([passportMiddleware])
  .validator((input: { title: string }) => {
    if (typeof input?.title !== 'string' || input.title.trim() === '') {
      throw new Error('Title is required');
    }
    return { title: input.title.trim() };
  })
  .handler(async ({ data, context }) => {
    const record = {
      action: 'request.created',
      actor: visitorKey(context.visitor),
      actorEmail: context.visitor.email ?? null,
      title: data.title,
      createdAt: new Date().toISOString(),
    };

    // Structured log line, visible in your project's logs.
    console.log(JSON.stringify(record));

    // Persist `record` to your database, keyed on `record.actor`.
    return { ok: true };
  });
```

Then connect the server function to a form. The form only collects the request title, since the middleware supplies the actor from Passport. Never read the actor from form fields or request bodies. The client controls those values, so anyone could submit a request on behalf of someone else.

For example, add the form to `src/routes/requests.tsx`:

```tsx
import { useState } from 'react';
import { createFileRoute } from '@tanstack/react-router';
import { useServerFn } from '@tanstack/react-start';
import { submitRequest } from '../lib/requests.functions';

export const Route = createFileRoute('/requests')({
  component: RequestsPage,
});

function RequestsPage() {
  const submit = useServerFn(submitRequest);
  const [title, setTitle] = useState('');

  return (
    <form
      onSubmit={async (event) => {
        event.preventDefault();
        await submit({ data: { title } });
        setTitle('');
      }}
    >
      <label htmlFor="title">Request title</label>
      <input
        id="title"
        name="title"
        value={title}
        onChange={(event) => setTitle(event.target.value)}
        required
      />
      <button type="submit">Submit request</button>
    </form>
  );
}
```

Passport also records sign-ins at the platform level. Each successful authentication creates a `passport-access-granted` event in the Activity Log and Audit Logs, so your application log covers what visitors did and Vercel's logs cover when they entered.

### Share the visitor with every route through route context

Internal tools often greet the visitor, show their own records, or prefill forms on most pages. Instead of loading the visitor in each route, load it once in the root route's `beforeLoad` and read it from route context in any route below it.

Create `src/lib/visitor.functions.ts` to return only the fields your routes need:

```typescript
import { createServerFn } from '@tanstack/react-start';
import { passportMiddleware } from './passport.middleware';

export const getCurrentVisitor = createServerFn({ method: 'GET' })
  .middleware([passportMiddleware])
  .handler(async ({ context }) => {
    const { visitor } = context;

    // Return only the fields the client needs. Never return visitor.token.
    return {
      subject: visitor.subject,
      email: visitor.email ?? null,
      name: visitor.name ?? null,
    };
  });
```

Then return the visitor from `beforeLoad` in your root route at `src/routes/__root.tsx`. Whatever `beforeLoad` returns is merged into route context for every child route:

```tsx
import { createRootRoute } from '@tanstack/react-router';
import { getCurrentVisitor } from '../lib/visitor.functions';

export const Route = createRootRoute({
  beforeLoad: async () => ({ visitor: await getCurrentVisitor() }),
  // Keep your existing root route options, such as head and shellComponent.
});
```

With `createRootRouteWithContext()`, add the same `beforeLoad` to that root route.

Read the visitor in any route with `Route.useRouteContext()`.

```tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/')({
  component: HomePage,
});

function HomePage() {
  const { visitor } = Route.useRouteContext();
  const displayName = visitor.name ?? visitor.email ?? 'there';

  return (
    <main>
      <h1>Welcome, {displayName}</h1>
      <p>You're signed in through your company identity provider.</p>
    </main>
  );
}
```

The `visitor` in route context is fully typed, so child routes don't need to check for a missing identity. When a request has no Passport identity, `passportMiddleware` throws and the router renders your error component instead.

During server-side rendering, `beforeLoad` calls the server function within the same request, so the middleware reads the page request's headers.

During client-side navigation, the call goes over HTTP, and the browser sends the Passport session cookie so Vercel injects the verified token before the request reaches your app. The root `beforeLoad` runs on every navigation, which means one small server function call per page change.

Profile fields such as `name` and `email` only appear when your identity provider returns them, so always provide a fallback. To load records that belong to the visitor, query your data store from a server function with `visitorKey(context.visitor)` rather than the email address.

### Lock down a section of your app with a pathless layout route

Passport lets everyone assigned to your identity provider application into the deployment, but an admin area or a destructive action usually needs a narrower policy. Group membership from your identity provider is a good fit, and a pathless layout route lets you apply it to a whole section of your app at once.

#### Request the `groups` claim

Passport requests the standard `openid`, `profile`, and `email` scopes by default.

To include group membership in the Passport token:

1. In the Vercel dashboard, open the Connect application that Passport uses from your project's Passport settings.
   
2. Under Grant Types, enable User Authorization.
   
3. Add `groups` to the User Authorization scopes.
   
4. Under Forwarded ID Token Claims, add `groups`. This is the allowlist of extra claims Connect can include in the Passport token.
   
5. Save the application.
   

Your identity provider must also return the claim.

In Okta, for example, you configure a `groups` claim on the authorization server that matches your Connect application's issuer. See [Configure additional identity scopes](https://vercel.com/docs/passport/additional-identity-scopes) for the full Okta walkthrough.

After changing the configuration, start a new Passport session in a private browser window so the new claim appears.

#### Build an admin middleware

Add a helper in `src/lib/authorization.server.ts` that reads `groups` defensively, because the claim name and shape depend on your identity provider:

```typescript
import type { PassportIdentity } from './passport.server';

export const ADMIN_GROUP = 'platform-admins';

export function getGroups(identity: PassportIdentity): string[] {
  const groups = identity.payload.groups;

  if (!Array.isArray(groups)) return [];
  return groups.filter((group): group is string => typeof group === 'string');
}

export function isInGroup(identity: PassportIdentity, group: string): boolean {
  return getGroups(identity).includes(group);
}
```

Then add `adminMiddleware` to `src/lib/passport.middleware.ts`.

It builds on `passportMiddleware`, so the visitor is already verified when the group check runs. It throws `notFound()` when the check fails, so visitors outside the group can't tell the admin area exists:

```typescript
// Add these imports to the top of the file.
import { notFound } from '@tanstack/react-router';
import { ADMIN_GROUP, isInGroup } from './authorization.server';

// Builds on passportMiddleware and requires membership in the admin group.
export const adminMiddleware = createMiddleware({ type: 'function' })
  .middleware([passportMiddleware])
  .server(async ({ next, context }) => {
    if (!isInGroup(context.visitor, ADMIN_GROUP)) {
      throw notFound();
    }

    return next();
  });
```

Attach `adminMiddleware` to every server function in the admin area.

Create `src/lib/admin.functions.ts` with a function the layout route calls and one privileged operation:

```typescript
import { createServerFn } from '@tanstack/react-start';
import { adminMiddleware } from './passport.middleware';

export const checkAdminAccess = createServerFn({ method: 'GET' })
  .middleware([adminMiddleware])
  .handler(async () => ({ allowed: true }));

export const rotateApiKey = createServerFn({ method: 'POST' })
  .middleware([adminMiddleware])
  .handler(async ({ context }) => {
    // context.visitor is the verified admin who made the request.
    console.log(`API key rotated by ${context.visitor.subject}`);

    // Perform the privileged operation here.
    return { rotated: true };
  });
```

#### Gate every admin route with a pathless layout

Pathless layout routes use a leading underscore, so they wrap their child routes without adding a segment to the URL. Create `src/routes/_admin.tsx` and call `checkAdminAccess()` from its `beforeLoad`:

```tsx
import { Outlet, createFileRoute } from '@tanstack/react-router';
import { checkAdminAccess } from '../lib/admin.functions';

export const Route = createFileRoute('/_admin')({
  beforeLoad: () => checkAdminAccess(),
  component: () => <Outlet />,
});
```

Every route file inside `src/routes/_admin/` now passes through the check.

For example, `src/routes/_admin/admin.tsx` serves `/admin`:

```tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/_admin/admin')({
  component: AdminPage,
});

function AdminPage() {
  return <h1>Admin settings</h1>;
}
```

TanStack Start passes a `notFound()` thrown in a server function back to the router, so visitors outside the group see your not-found component.

The layout gate only controls what visitors see. Server functions are reachable as their own endpoints, so a check in `beforeLoad` doesn't protect them, and `beforeLoad` also runs in the browser during client-side navigation. Attaching `adminMiddleware` to each privileged server function, as `rotateApiKey` does, is what enforces the policy on the server.

The local development identity doesn't include `groups`, so every admin route renders as not found while running `vite dev`. Test group checks on a Passport-protected preview deployment.

Don't try to make a path public from TanStack Start request middleware either. Request middleware runs after Passport has already made its sign-in decision, so it can add checks but can't bypass Passport.

### Forward Passport identity to another backend

Your TanStack Start app might call a separate API, such as a reporting service or another Vercel project, that also needs to know who the visitor is.

Forward the Passport token as a bearer token and verify it at the receiving service with `verifyIdentity()`.

#### Forward the token from the TanStack Start app

Create `src/routes/api/export-report.ts` with a server route:

```typescript
import { createFileRoute } from '@tanstack/react-router';
import { getVisitor } from '../../lib/passport.server';

export const Route = createFileRoute('/api/export-report')({
  server: {
    handlers: {
      POST: async () => {
        const visitor = await getVisitor();

        if (!visitor?.token) {
          return Response.json({ error: 'Unauthorized' }, { status: 401 });
        }

        const response = await fetch('https://reports.example.com/api/reports', {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${visitor.token}`,
          },
        });

        return new Response(response.body, { status: response.status });
      },
    },
  },
});
```

The development identity has no `token`, so this route returns `401` under `vite dev`. Test forwarding against a Passport-protected preview deployment.

#### Verify the token in the receiving backend

Install `@vercel/passport` in the receiving service. The `verifyIdentity()` function is available in `@vercel/passport` 0.1.3 and later.

`verifyIdentity()` reads the bearer token from the request, checks its signature, and confirms it came from the expected source project and environment. If the receiving service is also a TanStack Start app, pass the server route's `request` directly, like in `src/routes/api/reports.ts`:

```typescript
import { createFileRoute } from '@tanstack/react-router';
import { verifyIdentity } from '@vercel/passport';

export const Route = createFileRoute('/api/reports')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        try {
          // IDs of the Passport-protected deployment that minted the token,
          // not the project receiving it. Read env per request.
          const identity = await verifyIdentity(request, {
            ownerId: process.env.PASSPORT_SOURCE_OWNER_ID!,
            projectId: process.env.PASSPORT_SOURCE_PROJECT_ID!,
            environment: process.env.PASSPORT_SOURCE_ENVIRONMENT ?? 'production',
          });

          // Use identity.subject (with identity.payload.iss) as the visitor ID.
          return Response.json({ requestedBy: identity.subject });
        } catch {
          return Response.json({ error: 'Unauthorized' }, { status: 401 });
        }
      },
    },
  },
});
```

Keep these rules in mind when forwarding identity:

- Use HTTPS to backends you operate: The Passport token authenticates the visitor to your own services. It isn't an access token for your identity provider or for third-party APIs. Use Vercel Connect when a backend needs credentials for another service.
  
- Validate against the source deployment: Pass the owner ID, project ID, and environment of the app that minted the token. Don't substitute the receiving project's values.
  
- Verify outside JavaScript with the JWKS: Non-JavaScript services can verify the token as a standard JWT against https://passport.vercel.com/.well-known/jwks.json, checking `iss`, `aud`, `exp`, `nbf`, `typ` (which must equal `passport`), `project_id`, and `environment`.
  
- Account for Deployment Protection on the receiver: If the receiving backend is itself a protected Vercel deployment, the server-to-server request has no Passport session and needs a [Protection Bypass for Automation](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation) secret.
  

## Troubleshooting

### `getIdentity()` returns `null` on a deployment

Confirm that the deployment is Passport-protected and that `getVisitor()` runs inside server function middleware, a server function handler, or a server route handler. Requests that use Protection Bypass for Automation without an existing Passport session legitimately have no identity. If the route is prerendered or its response is cached, remove that configuration so the route runs per request.

`getIdentity()` also checks the token's `project_id` and `environment` claims against the `VERCEL_PROJECT_ID` and `VERCEL_TARGET_ENV` (or `VERCEL_ENV`) System Environment Variables. If those variables aren't exposed to your functions, verification throws and `getVisitor()` returns `null`. Confirm that **Enable access to System Environment Variables** is checked in your project’s [environment variable settings.](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fsettings%2Fenvironment-variables%3Fenv%3Dsystem)

### The `groups`, `email`, or `name` claim is missing

These claims are optional. Confirm that your identity provider returns the claim, that the Connect application requests the scope and lists the claim under Forwarded ID Token Claims, and then start a new Passport session. Existing sessions keep the claims from when they were issued.

### Webhooks or cron jobs can't reach a Passport-protected route

Passport redirects any request without a session to your identity provider, including webhooks and Cron Jobs. The original request must include a Protection Bypass for Automation secret. Don't try to make a path public from TanStack Start request middleware, because Passport has already run by then.

### Token verification fails

Treat the request as unauthenticated and confirm your code reads the Vercel-injected `x-vercel-oidc-passport-token` header, not a client-supplied value. For forwarded tokens, check that the server can reach the Passport JWKS and that its clock is accurate. The `@vercel/passport` SDK fetches signing keys from https://oidc.vercel.com/.well-known/jwks, which serves the same key set as https://passport.vercel.com/.well-known/jwks.json, so allow both hosts if your backend restricts outbound traffic. Don't log the raw token while debugging.

## Next steps

- Review every claim in the [Passport token claims reference](https://vercel.com/docs/passport/token-claims).
  
- Read the full [Read Passport identity in your application](https://vercel.com/docs/passport/read-identity) docs for SDK options.
  
- Set up additional claims with [Configure additional identity scopes](https://vercel.com/docs/passport/additional-identity-scopes).
  
- Learn more about [forwarding identity](https://vercel.com/docs/passport/forward-identity) and [verifying forwarded tokens](https://vercel.com/docs/passport/verify-identity).
  
- Compare Passport with other [methods to protect deployments](https://vercel.com/docs/deployment-protection/methods-to-protect-deployments).
  
- Learn more about [TanStack Start middleware](https://tanstack.com/start/latest/docs/framework/react/guide/middleware).
  
- See how [TanStack Start runs on Vercel](https://vercel.com/docs/frameworks/full-stack/tanstack-start).
  

## Frequently asked questions

### How do I get the current user in TanStack Start with Vercel Passport?

Install `@vercel/passport` and call `getIdentity()` from a server function or server route, passing the request headers from `getRequestHeaders()`. It returns a verified identity object with `subject`, `externalSubject`, `email`, `name`, and the full claim `payload`, or `null` when the request has no Passport identity.

### Should I use `sub` or `external_sub` to identify a Vercel Passport visitor?

Use `sub` (exposed as `identity.subject`) as the stable visitor identifier, and store it together with the `iss` claim if your app accepts tokens from more than one Vercel team. The `external_sub` claim comes from your identity provider, is specific to the Connect application, and isn't globally unique.

### Can I use Vercel Passport identity in a React component?

Not directly. Read identity in a server function, then return only the fields you need to your components, for example by returning the visitor from the root route's `beforeLoad` and reading it with `useRouteContext()`. Never send the raw Passport token to the browser.

### How do I test Vercel Passport locally?

A local `vite dev` server receives a development identity from `getIdentity()`, which you can customize with `VERCEL_PASSPORT_DEV_*` environment variables in `.env.local` or disable with `VERCEL_PASSPORT_DEV=0`. To test the real identity provider sign-in and verified claims, use a Passport-protected preview deployment.

### Can I use `beforeLoad` or middleware to authorize Vercel Passport visitors?

Use `beforeLoad` to decide what a visitor sees, but don't treat it as a security boundary, because it also runs in the browser and doesn't protect your server functions. Enforce authorization in server function middleware, such as a group-checking `adminMiddleware`, attached to every privileged server function. No TanStack Start middleware can make a Passport-protected path public, because Passport runs before your app.