---
title: Using Vercel Passport with Microsoft Entra ID
description: Configure Microsoft Entra ID and Vercel Passport to protect a Next.js app, limit access to assigned users, and read verified visitor identities.
url: "https://vercel.com/kb/guide/vercel-passport-microsoft-entra-id"
published: 2026-10-15
last_updated: 2026-10-15
authors: Anshuman Bhardwaj
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Use [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) with [Vercel Passport](https://vercel.com/docs/passport) to control access to an internal app deployed on Vercel. Entra ID authenticates visitors and checks their application assignments. Passport handles sign-in before requests reach your app and forwards a signed identity token to your server.

You’ll configure access for selected users in a single Microsoft Entra ID tenant and add a server-side identity endpoint to a Next.js app.

**Microsoft Entra ID + Vercel Passport**

Control who can access your internal app deployed on Vercel.

[Deploy Template](https://vercel.link/4dCiH8I)

If you deploy the template, follow **Configure a Microsoft Entra ID application**, then skip to **Connect Entra ID to Vercel Passport**.

## Overview

When a visitor opens the Next.js app on a Passport-protected Vercel deployment, sign-in follows this sequence:

1. The visitor opens a Passport-protected deployment.
   
2. Passport redirects the visitor to Entra ID through OpenID Connect.
   
3. Entra ID authenticates the visitor and checks their application assignment.
   
4. Passport creates a session and forwards a signed identity token to your app.
   
5. Your server verifies the token before displaying the visitor's identity.
   

## Prerequisites

Before you begin, you need:

- Node.js 22+, npm, and the [Vercel CLI](https://vercel.com/docs/cli).
  
- Owner role in a Vercel team on the [Enterprise plan](https://vercel.com/docs/plans/enterprise) with Passport access.
  
- A Microsoft Entra ID workforce tenant and permission to register applications, assign users, and grant admin consent.
  

## Configure a Microsoft Entra ID application

### Step 1. Register an application in Microsoft Entra ID

1. Open the [Microsoft Entra admin center](https://entra.microsoft.com/).
   
2. Go to **Entra ID > App registrations > New registration**.
   
3. Enter a name, such as **Internal Engineering Portal**.
   
4. Under **Supported account types**, select **Accounts in this organizational directory only**.
   
5. Under **Redirect URI**, select **Web** and enter:
   
   ```text
   https://connect.vercel.com/callback
   ```
   
6. Select **Register**.
   
7. On the application’s **Overview** page, copy the **Application (client) ID** and **Directory (tenant) ID**.
   

Passport receives the authentication response at this address. You can [update the redirect URI](https://learn.microsoft.com/en-us/entra/identity-platform/how-to-add-redirect-uri) on the app registration's **Authentication** page.

### Step 2. Create a client secret

1. In the app registration, open **Certificates & secrets**.
   
2. Select **Client secrets > New client secret**.
   
3. Enter a description, such as **Vercel Passport**.
   
4. Choose an expiration that follows your organization’s policy.
   
5. Select **Add**.
   
6. Copy the secret’s **Value** immediately.
   

Copy the value, not the **Secret ID**. Entra ID does not display the value again after you leave the page.

You'll enter this secret when connecting Entra ID to Passport. Follow Microsoft's [credential instructions](https://learn.microsoft.com/en-us/entra/identity-platform/how-to-add-credentials) to add or manage application credentials.

### Step 3. Configure permissions and admin consent

The connection requests the `openid`, `profile`, and `email` scopes. Microsoft’s [OpenID Connect documentation](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc) explains the identity information available through these scopes and the UserInfo endpoint.

1. In the app registration, open **API permissions**.
   
2. Select **Add a permission > Microsoft Graph > Delegated permissions**.
   
3. Under **OpenId permissions**, select `openid`, `profile`, and `email`.
   
4. Select **Add permissions**.
   
5. Review the complete permission list.
   
6. Select **Grant admin consent for your tenant** and confirm.
   
7. Check that the permissions show consent as granted.
   

Applications that require user assignment also require an administrator to [grant tenant-wide admin consent](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/grant-admin-consent).

### Step 4. Restrict access to assigned users

Configure access on your app registration's enterprise application.

1. Go to **Entra ID > Enterprise apps > All applications**.
   
2. Find the application you registered, such as Internal Engineering Portal.
   
3. Open **Properties**.
   
4. Set **Assignment required?** to **Yes**.
   
5. Confirm that **Enabled for users to sign-in?** is **Yes**.
   
6. Save the changes.
   

Microsoft's [application properties reference](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/application-properties) covers these settings.

Choose two test accounts in your tenant that do not have the Global Administrator role. Global Administrators can sign in regardless of the assignment requirement. Assign one test account to the application:

1. Open **Users and groups > Add user/group**.
   
2. Select the account under **Users and groups**.
   
3. Choose **Default Access** if no application roles are defined.
   
4. Select **Assign**.
   

Leave the second test account unassigned.

With Microsoft Entra ID P1 or P2, you can [assign a group](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/assign-user-or-group-access-portal) instead of individual users. Only direct group members receive access through the assignment.

## Integrate Vercel Passport

### Step 1. Create a Next.js app

Create a Next.js app with TypeScript and the App Router. Then, install the Passport helper, and create the directories for the identity helper and API route:

```bash
npx create-next-app@latest passport-entra-id --typescript --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm --yes
cd passport-entra-id
npm install @vercel/passport server-only
mkdir -p src/lib src/app/api/me
```

### Step 2. Read the Passport identity on the server

After authentication, Passport forwards a signed identity token in the `x-vercel-oidc-passport-token` header. The `getIdentity` helper reads and verifies it.

Create `src/lib/identity.ts` with a `readVisitor` helper that both the page and API route can use:

```typescript
import "server-only";
import { getIdentity } from "@vercel/passport";

export type Visitor = {
  id: string;
  name: string | null;
  email: string | null;
  demo: boolean;
};

export async function readVisitor(
  requestHeaders: Headers,
): Promise<Visitor | null> {
  if (
    process.env.NODE_ENV === "development" &&
    !process.env.VERCEL &&
    process.env.PORTAL_DEMO === "true"
  ) {
    return {
      id: "local-demo",
      name: "Alex Morgan",
      email: "alex@example.com",
      demo: true,
    };
  }
  if (!requestHeaders.get("x-vercel-oidc-passport-token")) {
    return null;
  }
  try {
    const identity = await getIdentity(
      { headers: requestHeaders },
      { development: false },
    );
    if (!identity?.verified || !identity.externalSubject) {
      return null;
    }
    return {
      id: identity.externalSubject,
      name: identity.name ?? null,
      email: identity.email ?? null,
      demo: false,
    };
  } catch {
    return null;
  }
}
```

Outside local demo mode, `readVisitor` returns visitor details only when the identity is verified and includes an external subject. It returns `null` if the token is missing or identity verification fails.

Setting `development: false` disables the Passport helper’s automatic development identity. Instead, `readVisitor` supplies a sample identity when `PORTAL_DEMO=true` during local development. This demo is disabled in production and on Vercel.

This example uses `identity.externalSubject` to identify the visitor within the configured identity provider. If your app supports multiple Connect applications, have `readVisitor` return `identity.subject`.

Name and email are optional. See the [Passport helper documentation](https://www.npmjs.com/package/@vercel/passport).

### Step 3. Expose the identity through an API

Create `src/app/api/me/route.ts` to return the visitor’s identity at `/api/me`:

```typescript
import { readVisitor } from "@/lib/identity";

export async function GET(request: Request) {
  const visitor = await readVisitor(request.headers);
  const headers = { "Cache-Control": "private, no-store" };
  if (!visitor) {
    return Response.json({ authenticated: false }, { status: 401, headers });
  }
  return Response.json(
    {
      authenticated: !visitor.demo,
      demo: visitor.demo,
      user: { id: visitor.id, name: visitor.name, email: visitor.email },
    },
    { headers },
  );
}
```

Requests that reach the endpoint without a valid identity return `401` when local demo mode is disabled. Passport redirects unauthenticated browser requests to the identity provider before they reach a protected deployment.

### Step 4. Access the identity from a page

Replace `src/app/page.tsx` with this page to display the visitor’s identity. This code omits the template’s CSS and font configuration:

```tsx
import { Suspense } from "react";
import { headers } from "next/headers";
import { readVisitor } from "@/lib/identity";

export default function Home() {
  return (
    <Suspense
      fallback={
        <main>
          <p>Checking access…</p>
        </main>
      }
    >
      <IdentityStatus />
    </Suspense>
  );
}

async function IdentityStatus() {
  const visitor = await readVisitor(await headers());
  const state = !visitor ? "denied" : visitor.demo ? "demo" : "granted";

  return (
    <main>
      <header>
        <p className="label">Microsoft Entra ID / Vercel Passport</p>
        <h1>Identity and access</h1>
      </header>
      <section className="identity-card" aria-label="Identity status">
        <p role="status" className={`status ${state}`}>
          <span aria-hidden="true" className="status-dot" />
          {!visitor
            ? "Access denied"
            : visitor.demo
              ? "Local demo"
              : "Access granted"}
        </p>
        <p className="description">
          {!visitor
            ? "No verified identity. Sign in through a Passport-protected deployment."
            : visitor.demo
              ? "Sample identity for local development. No Entra sign-in took place."
              : "Your identity is verified through Vercel Passport."}
        </p>
        {visitor ? (
          <>
            <dl>
              <div>
                <dt>Name</dt>
                <dd>{visitor.name ?? "Not provided"}</dd>
              </div>
              <div>
                <dt>Email</dt>
                <dd>{visitor.email ?? "Not provided"}</dd>
              </div>
              <div>
                <dt>Visitor ID</dt>
                <dd className="mono">{visitor.id}</dd>
              </div>
            </dl>
            <a href="/api/me">
              View identity response <span aria-hidden="true">↗</span>
            </a>
          </>
        ) : null}
      </section>
    </main>
  );
}
```

The page checks the identity on the server before displaying the visitor’s information. Passport handles sign-in before the request reaches your app, so you don’t need to add a login flow to this example.

### Step 5. Test locally

To preview with a sample identity, add this setting to `.env.local`:

```bash
PORTAL_DEMO=true
```

Start the development server:

```bash
npm run dev
```

Open `http://localhost:3000`, then visit `/api/me`. The page shows **Local demo** and the API returns `authenticated: false` with `demo: true`.

Set `PORTAL_DEMO=false` and restart the server to test requests without an identity. The page shows **Access denied** and `/api/me` returns `401`.

### Step 6. Deploy to production

Deploy to Vercel to configure Passport and test Entra ID sign-in:

```bash
vercel deploy --prod
```

On the first deployment, follow the setup prompts:

1. Confirm that you want to set up and deploy the current directory.
   
2. Select your Vercel Enterprise team with Passport access.
   
3. Choose to create a new project.
   
4. Name it `passport-entra-id`.
   
5. Use `./` as the code directory.
   
6. Accept the detected Next.js settings.
   

## Connect Entra ID to Vercel Passport

Connect your Microsoft Entra ID application to the deployed project:

1. Open your project in the Vercel dashboard.
   
2. Go to **Settings > Passport**.
   
3. Enable Passport.
   
4. Create a Vercel Connect application, choose **OAuth**, and select **Your own credentials**.
   
5. In Server URL, enter the following issuer URL, replacing `<tenant-id>` with your Directory (tenant) ID:
   
   ```text
   https://login.microsoftonline.com/<tenant-id>/v2.0
   ```
   
6. Select **Discover**.
   
7. Enter your Application (client) ID in **Client ID** and your client secret’s value in **Client Secret**.
   
8. In the Connect application, enable **User Authorization** under **Grant Types** and confirm that its scopes include `openid`, `profile`, and `email`.
   
9. Select **Create Connector**. Choose the new Connect application in your project’s Passport settings, then select **Save**.
   

### Test allowed and denied access

#### Assigned user

1. Visit your Passport-protected production URL in a new private session.
   
2. Sign in with the assigned test account and check for **Access granted**.
   
3. Open `/api/me` on the same deployment and confirm that it returns `authenticated: true` and the visitor's ID. Name and email may be `null` if Entra ID does not return those fields.
   

#### Unassigned user

1. Close the private session and open a new one.
   
2. Visit the same protected URL.
   
3. Sign in with the unassigned test account.
   
4. Confirm that Entra ID denies access and the app does not load.
   

Test assignment changes in a fresh private browser session. Existing authenticated sessions can remain valid until they expire.

## Troubleshooting

| Problem                      | What to check                                                                                                                                           |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Redirect URI mismatch        | The app registration’s Web redirect URI must exactly match `https://connect.vercel.com/callback`.                                                       |
| Consent or approval required | Confirm that an administrator granted consent for the configured permissions.                                                                           |
| Assigned user cannot sign in | Confirm that the account is assigned to the correct enterprise application and sign-in is enabled.                                                      |
| Unassigned user can sign in  | Confirm that “Assignment required?” is set to Yes, then retest in a fresh private session. Check whether the account has the Global Administrator role. |
| Client authentication fails  | Check that Passport has the secret’s value rather than its ID and that the secret has not expired.                                                      |
| `/api/me` returns `401`      | Check that you are signed in on a Passport-protected deployment. Local requests return `401` when demo mode is disabled.                                |

For failed Entra ID sign-ins, open **Entra ID > Enterprise apps > your application > Sign-in logs** and inspect the failure details.

## Next steps

- Read the [Vercel Passport guide](https://vercel.com/kb/guide/vercel-passport) for team-wide setup and troubleshooting.
  
- Explore the [template repository](https://github.com/vercel-labs/nextjs-passport-entra-id) for the complete working app.
  
- Review [Passport identity claims, token verification, and additional scopes](https://vercel.com/docs/passport).
  
- Manage users and app access with the [Entra ID documentation](https://learn.microsoft.com/en-us/entra/identity/).
  

## Frequently asked questions

### Does Vercel Passport support Microsoft Entra ID?

Yes. Vercel Passport supports Microsoft Entra ID through OpenID Connect. Create an OAuth Connect application, select Your own credentials, and enter Entra ID’s tenant-specific issuer URL, client ID, and client secret. Follow the [Passport setup instructions](https://vercel.com/docs/passport/set-up-identity-provider#microsoft-entra-id).

### What redirect URI should I use for Entra ID and Vercel Passport?

Register `https://connect.vercel.com/callback` as a **Web** redirect URI in your Microsoft Entra ID app registration so Passport can receive the authentication response. Use **Authentication** to [add or update the URI](https://learn.microsoft.com/en-us/entra/identity-platform/how-to-add-redirect-uri).

### How do I restrict a Passport-protected app to specific Entra ID users?

Set **Assignment required?** to **Yes** on the Microsoft Entra ID enterprise application, grant admin consent, and assign users who need access. [Users can sign in without an assignment](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/what-is-access-management) when **Assignment required?** is **No**.

Global Administrators are [exempt from the assignment requirement](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/application-properties).

### Can I use Entra ID groups to control access?

Yes. Microsoft Entra ID P1 or P2 supports group-based application assignment. Set the enterprise application to require assignment, then [assign the group](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/assign-user-or-group-access-portal). Nested group membership does not grant access through that assignment.

### How does a Next.js app identify a visitor after Passport sign-in?

Passport sends a signed token in the `x-vercel-oidc-passport-token` request header. Your server verifies it with `getIdentity` from `@vercel/passport`.

This example uses `identity.externalSubject` for the visitor's ID in the configured identity provider. Name and email may be absent. Consult the [Passport helper documentation](https://www.npmjs.com/package/@vercel/passport) for its supported options.

### Can I test Microsoft Entra ID sign-in locally?

No. Local requests do not pass through Passport, so test Microsoft Entra ID sign-in on a Passport-protected Vercel deployment.

For a local preview, enable `PORTAL_DEMO=true` to use a sample identity. The template's `/api/me` endpoint then returns `authenticated: false` with `demo: true`. With demo mode disabled, requests without an identity return `401`. Follow the [template's local setup instructions](https://github.com/vercel-labs/nextjs-passport-entra-id#run-locally) to configure the demo.

## More Next.js guides

- [How to deploy a Python backend with Vercel Services](/kb/guide/python-backend-on-vercel): Deploy a Python backend with a Next.js frontend on Vercel. Learn how to configure both apps with Vercel Services, use a separate database for each environment, and review changes at a Preview URL.
- [Identify users and control access in Next.js with Vercel Passport](/kb/guide/vercel-passport-nextjs): Use \`getIdentity()\` to read a visitor’s verified Vercel Passport identity in Next.js, then personalize pages, restrict routes by group, and log actions.
- [How can I use Python and JavaScript in the same application?](/kb/guide/how-to-use-python-and-javascript-in-the-same-application): Unlock the power of Python and JavaScript in your apps. Learn to integrate Flask and Next.js for dynamic frontends with AI-capable backends. Perfect for developers keen on hybrid programming.