Skip to content
Docs

Connect to Microsoft Azure

Secure backend access with OIDC federation is available on all plans

Connect your Vercel deployments to Microsoft Azure without storing long-lived credentials. Microsoft Entra ID trusts Vercel's OIDC Identity Provider (IdP) and exchanges the token Vercel issues for a short-lived Entra ID access token.

To understand how Azure supports OIDC through Workload Identity Federation, consult the Azure documentation.

OIDC federation works with any endpoint that accepts a Microsoft Entra ID access token. This includes managed Azure services:

  • Databases and storage: Azure Cosmos DB, Azure SQL Database, Azure Storage (Blob, Queue, Table, and Data Lake), Azure Managed Redis, and Azure Cache for Redis
  • Messaging and eventing: Azure Event Hubs, Azure Service Bus, and Azure Event Grid
  • Security: Azure Key Vault and Azure App Configuration
  • AI: Azure AI Foundry and Azure AI Search

It also includes APIs you build and host yourself. If your API accepts Entra ID tokens, your Vercel deployment can call it with a federated credential regardless of where it runs: whether that's Azure Kubernetes Service (AKS), App Service, Container Apps, Azure Functions, or a virtual machine.

OIDC federation covers identity, not network reachability. If your Azure resources reject public traffic or restrict access by IP address, see Using Vercel with Microsoft Azure for your network connectivity options.

    • Navigate to Microsoft Entra ID in the Azure portal
    • Select App registrations and select New registration
    • Enter a Name for your own reference, such as Vercel, keep the default single-tenant option, and select Register
    • From the app registration's Overview page, copy the Application (client) ID and the Directory (tenant) ID. You'll add both to your Vercel project as environment variables

    Registering an application creates a service principal in your tenant. That service principal is the identity your Vercel deployments act as, and you never need to create a client secret for it.

    • From your app registration, go to Certificates & secrets, open the Federated credentials tab, and select Add credential
    • In the Federated credential scenario field select Other
    • Enter the Issuer URL, the URL will depend on the issuer mode setting:
      • Team: https://oidc.vercel.com/[TEAM_SLUG], replacing [TEAM_SLUG] with the path from your Vercel team URL
      • Global: https://oidc.vercel.com
    • In the Subject identifier field use: owner:[TEAM_SLUG]:project:[PROJECT_NAME]:environment:[preview | production | development]
    • In the Name field, use a name for your own reference such as: [Project name] - [Environment]
    • In the Audience field, you have two options:
      • Default: Enter https://vercel.com/[TEAM_SLUG], replacing [TEAM_SLUG] with your team identifier from the Vercel team URL
      • Recommended: Enter api://AzureADTokenExchange. When using this value, you must also pass a matching audience in your code. See the custom audience section below

    Azure does not allow for partial claim conditions so you must specify the Subject and Audience fields exactly. However, it is possible to create multiple federated credentials on the same app registration to allow for the various sub claims.

  1. To connect to the Azure service that you would like to use, grant your service principal access to it. How you do that depends on the service:

    • Azure RBAC: for services such as Azure Storage, Azure Key Vault, and Azure Service Bus, assign a built-in role to the service principal on the resource, such as Storage Blob Data Contributor or Key Vault Secrets User
    • Azure Cosmos DB: assign a data plane role definition to the service principal with the Azure CLI, as explained in the Azure Cosmos DB documentation
    • Azure SQL Database: create a contained database user for the app registration with CREATE USER [your_app_registration_name] FROM EXTERNAL PROVIDER, then grant it the database roles it needs. See the Azure SQL documentation
    • Your own API: expose an app role or scope on the API's app registration, assign it to your Vercel app registration, and validate the resulting token in your API

    You are now ready to connect to your Azure service from your project's code. Review the example below.

By default, the OIDC token's aud claim is set to https://vercel.com/[TEAM_SLUG]. Azure recommends using api://AzureADTokenExchange as the audience for workload identity federation. To use this value, pass the audience option to getVercelOidcToken:

import { getVercelOidcToken } from '@vercel/oidc';
 
const token = await getVercelOidcToken({
  audience: 'api://AzureADTokenExchange',
});

When using a custom audience, set the Audience field in your federated credential to the same value (api://AzureADTokenExchange).

In the following example, you create a Vercel function in a Vercel project where you have defined Azure account environment variables. The function will connect to Azure using OIDC and use a specific resource that you have granted the service principal access to.

ClientAssertionCredential from @azure/identity accepts the Vercel OIDC token as a client assertion, so the same pattern works with every Azure SDK client that takes a credential.

Install the following packages:

Terminal
pnpm i @azure/identity @azure/cosmos @vercel/oidc
Terminal
yarn add @azure/identity @azure/cosmos @vercel/oidc
Terminal
npm i @azure/identity @azure/cosmos @vercel/oidc
Terminal
bun add @azure/identity @azure/cosmos @vercel/oidc

In the API route for this function, use the following code to perform a database SELECT query from an Azure Cosmos DB instance:

/api/azure-cosmosdb/route.ts
import { ClientAssertionCredential } from '@azure/identity';
import * as cosmos from '@azure/cosmos';
import { getVercelOidcToken } from '@vercel/oidc';
 
/**
 * The Microsoft Entra ID tenant (directory) ID.
 * Added to environment variables
 */
const AZURE_TENANT_ID = process.env.AZURE_TENANT_ID!;
 
/**
 * The client (application) ID of an App Registration in the tenant.
 * Added to environment variables
 */
const AZURE_CLIENT_ID = process.env.AZURE_CLIENT_ID!;
const COSMOS_DB_ENDPOINT = process.env.COSMOS_DB_ENDPOINT!;
const COSMOS_DB_ID = process.env.COSMOS_DB_ID!;
const COSMOS_DB_CONTAINER_ID = process.env.COSMOS_DB_CONTAINER_ID!;
 
const tokenCredentials = new ClientAssertionCredential(
  AZURE_TENANT_ID,
  AZURE_CLIENT_ID,
  getVercelOidcToken,
);
 
const cosmosClient = new cosmos.CosmosClient({
  endpoint: COSMOS_DB_ENDPOINT,
  aadCredentials: tokenCredentials,
});
 
const container = cosmosClient
  .database(COSMOS_DB_ID)
  .container(COSMOS_DB_CONTAINER_ID);
 
export async function GET() {
  const { resources } = await container.items
    .query('SELECT * FROM my_table')
    .fetchAll();
 
  return Response.json(resources);
}

This example uses Azure's recommended api://AzureADTokenExchange audience:

/api/azure-cosmosdb/route.ts
import { ClientAssertionCredential } from '@azure/identity';
import * as cosmos from '@azure/cosmos';
import { getVercelOidcToken } from '@vercel/oidc';
 
const AZURE_TENANT_ID = process.env.AZURE_TENANT_ID!;
const AZURE_CLIENT_ID = process.env.AZURE_CLIENT_ID!;
const COSMOS_DB_ENDPOINT = process.env.COSMOS_DB_ENDPOINT!;
const COSMOS_DB_ID = process.env.COSMOS_DB_ID!;
const COSMOS_DB_CONTAINER_ID = process.env.COSMOS_DB_CONTAINER_ID!;
 
const tokenCredentials = new ClientAssertionCredential(
  AZURE_TENANT_ID,
  AZURE_CLIENT_ID,
  () => getVercelOidcToken({ audience: 'api://AzureADTokenExchange' }),
);
 
const cosmosClient = new cosmos.CosmosClient({
  endpoint: COSMOS_DB_ENDPOINT,
  aadCredentials: tokenCredentials,
});
 
const container = cosmosClient
  .database(COSMOS_DB_ID)
  .container(COSMOS_DB_CONTAINER_ID);
 
export async function GET() {
  const { resources } = await container.items
    .query('SELECT * FROM my_table')
    .fetchAll();
 
  return Response.json(resources);
}

Install the following packages:

Terminal
pnpm i @azure/identity @azure/keyvault-secrets @vercel/oidc
Terminal
yarn add @azure/identity @azure/keyvault-secrets @vercel/oidc
Terminal
npm i @azure/identity @azure/keyvault-secrets @vercel/oidc
Terminal
bun add @azure/identity @azure/keyvault-secrets @vercel/oidc
/api/azure-key-vault/route.ts
import { ClientAssertionCredential } from '@azure/identity';
import { SecretClient } from '@azure/keyvault-secrets';
import { getVercelOidcToken } from '@vercel/oidc';
 
const AZURE_TENANT_ID = process.env.AZURE_TENANT_ID!;
const AZURE_CLIENT_ID = process.env.AZURE_CLIENT_ID!;
const KEY_VAULT_URL = process.env.KEY_VAULT_URL!;
 
const tokenCredentials = new ClientAssertionCredential(
  AZURE_TENANT_ID,
  AZURE_CLIENT_ID,
  () => getVercelOidcToken({ audience: 'api://AzureADTokenExchange' }),
);
 
const secretClient = new SecretClient(KEY_VAULT_URL, tokenCredentials);
 
export async function GET() {
  const secret = await secretClient.getSecret('your_secret_name_here');
 
  return Response.json({ value: secret.value });
}

To call an API you host yourself, request an access token scoped to that API and send it as a bearer token. This works wherever the API runs, including AKS, App Service, Container Apps, or a VM.

Before your code can request the token, expose an app role on the API's own app registration and assign that role to the app registration you created for Vercel, as described in Grant access to the Azure service.

Install the following packages:

Terminal
pnpm i @azure/identity @vercel/oidc
Terminal
yarn add @azure/identity @vercel/oidc
Terminal
npm i @azure/identity @vercel/oidc
Terminal
bun add @azure/identity @vercel/oidc
/api/azure-backend/route.ts
import { ClientAssertionCredential } from '@azure/identity';
import { getVercelOidcToken } from '@vercel/oidc';
 
const AZURE_TENANT_ID = process.env.AZURE_TENANT_ID!;
const AZURE_CLIENT_ID = process.env.AZURE_CLIENT_ID!;
 
/**
 * The Application ID URI of your API's app registration,
 * such as api://your_api_client_id_here
 * Added to environment variables
 */
const API_APP_ID_URI = process.env.API_APP_ID_URI!;
const API_BASE_URL = process.env.API_BASE_URL!;
 
const tokenCredentials = new ClientAssertionCredential(
  AZURE_TENANT_ID,
  AZURE_CLIENT_ID,
  () => getVercelOidcToken({ audience: 'api://AzureADTokenExchange' }),
);
 
export async function GET() {
  const { token } = await tokenCredentials.getToken(
    `${API_APP_ID_URI}/.default`,
  );
 
  const response = await fetch(`${API_BASE_URL}/orders`, {
    headers: { Authorization: `Bearer ${token}` },
  });
 
  if (!response.ok) {
    return new Response('Request to the backend API failed', { status: 502 });
  }
 
  return Response.json(await response.json());
}

Two different audiences are in play.
First, api://AzureADTokenExchange is the audience of the Vercel OIDC token, which Entra ID checks when it exchanges your federated credential.
Second, the .default scope built from your API's Application ID URI sets the aud claim of the access token that your API receives, which is the value your API validates.

The credential caches the access token and refreshes it before expiry, so calling getToken on every request does not trigger a new token exchange each time.

Last updated September 18, 2026

Was this helpful?

supported.