After Vercel Passport authenticates a visitor, you can identify that visitor in your Next.js app and decide what data they see. Vercel injects a signed token into the x-vercel-oidc-passport-token request header, and that token carries the visitor's identity claims from your identity provider. Because Vercel validates the session and injects the token server-side, your code can read the visitor's identity without running its own sign-in flow. Use the external_sub claim as the visitor's stable identifier to scope database queries, gate routes, and personalize responses.
This guide will show you how to read the Passport token from a server-side request in Next.js, decode it to access the visitor's identity claims, and use the external_sub claim to authorize a visitor and scope their data. It also covers how to handle requests where the token is missing.
Before you begin, make sure you have:
- Passport enabled on your project, currently available for Enterprise customers. See Restrict access to deployments with Passport to turn it on.
- A Next.js app deployed to Vercel. Vercel injects the Passport token only for protected deployments, so the header isn't present in local development.
- Code that runs on the server, such as a Route Handler, a Server Component, or a server action. The token is never available in client components or in the browser.
Vercel forwards the Passport session token to your deployment in the x-vercel-oidc-passport-token request header. The token is a Vercel-signed JWT that carries deployment context and the visitor's identity claims. The reliable user identifier is the external_sub claim, which comes from the external subject your identity provider returns.
You don't need to verify the token's signature in your own code. Vercel strips any client-supplied value from this header and injects the verified token after validating the session, so a token present in a server-side request is already trustworthy. Your job is to read the header and decode the claims.
The token carries the following claims:
| Claim | Description |
|---|---|
external_sub | The stable visitor identifier returned by your identity provider. Use this to identify the visitor. |
sub | The owner, connector_id, and external_sub in a stable Vercel format. |
scope | Includes the owner, connector_id, and external_sub. |
email, name | Profile fields. Not guaranteed. They appear only if your identity provider returns them in the Passport user info response. |
The Passport token is separate from the OIDC federation token in the x-vercel-oidc-token header. The federation token is for exchanging for short-lived cloud credentials with the @vercel/oidc helpers, while the Passport token identifies the human visiting your protected deployment.
Read the x-vercel-oidc-passport-token header in a server-side Route Handler. If the header is missing, the request didn't pass through Passport, so return a 401 response.
Missing tokens usually mean the request came from local development or an unprotected deployment. Guarding for it keeps your code working in both places.
Decode the token's payload to read the claims. Vercel has already validated the session, so you decode the JWT rather than verifying its signature. The jose library's decodeJwt reads the claims and works regardless of runtime.
Install jose:
pnpm i joseThen add a helper that reads and decodes the token in one place:
If you'd rather avoid a dependency and you're on the Node.js runtime, decode the payload segment directly:
Buffer is available on the Node.js runtime. The jose approach works across runtimes, so prefer it if your route runs anywhere other than Node.js.
Use the external_sub claim to decide what the visitor can access, and key your queries on it so each visitor reads only their own records. Derive the identity from the token, not from request parameters. A userId taken from the query string or request body lets a visitor request another visitor's data.
To restrict a route to specific visitors, check external_sub against an allowlist and return 403 for everyone else:
Server Components read request headers through headers() from next/headers, so you can identify the visitor when rendering a page. headers() returns a promise, so await it.
- Use
external_subas the identifier. It's stable and comes from your identity provider. Treatemailandnameas optional, since they appear only when your provider returns them. - Always read the token server-side. Vercel strips client-supplied header values, so the value you read in server code is the one Vercel injected and validated.
- Derive identity from the token, not from request parameters. Scoping a query to a
userIdfrom the query string or body lets a visitor request another visitor's data. - Use the right status codes. Return
401when the token is missing and403when the visitor is authenticated but not allowed.
- Confirm Passport is enabled on the project and that you're requesting a protected deployment URL.
- Confirm your code runs on the server. The header isn't available in client components or in the browser.
- In local development, the header isn't present because requests don't pass through Passport. Guard for a missing token so local development still works.
email and name appear only if your identity provider returns them in the Passport user info response. Use external_sub to identify the visitor and treat profile fields as optional.
- Learn how Passport fits alongside other methods in Deployment Protection.
- Compare methods to protect deployments to choose the right one for your project.
- Read the Passport documentation to enable and configure the feature.