You are currently viewing documentation for v1.8 (Beta)

Microsoft

Microsoft provider setup and usage.

Enabling OAuth with Microsoft Azure Entra ID (formerly Active Directory) allows your users to sign in and sign up to your application with their Microsoft account.

Get your Microsoft credentials

To use Microsoft as a social provider, you need to get your Microsoft credentials. This involves generating a Client ID in your Microsoft Entra ID dashboard and, for confidential clients, configuring either a Client Secret or a client assertion credential.

Make sure to set the redirect URL to http://localhost:3000/api/auth/callback/microsoft for local development. For production, you should change it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.

see the Microsoft Entra ID documentation for more information.

Configure the provider

To configure the provider, pass the clientId and, for confidential clients, either clientSecret or clientAssertion to socialProviders.microsoft in your auth configuration.

auth.ts
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    socialProviders: {
        microsoft: { 
            clientId: process.env.MICROSOFT_CLIENT_ID as string, 
            clientSecret: process.env.MICROSOFT_CLIENT_SECRET as string, 
            // Optional
            tenantId: 'common', 
            authority: "https://login.microsoftonline.com", // Authentication authority URL
            prompt: "select_account", // Forces account selection
        }, 
    },
})

Authority URL: Use the default https://login.microsoftonline.com for standard Entra ID scenarios or https://<tenant-id>.ciamlogin.com for CIAM (Customer Identity and Access Management) scenarios.

Client assertions: Use clientAssertion instead of clientSecret when your Microsoft Entra ID app is configured for private_key_jwt or workload identity federation. The callback receives the token request context and must return a JWT assertion. Do not combine clientAssertion with clientSecret.

auth.ts
import { getVercelOidcToken } from "@vercel/oidc"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    socialProviders: {
        microsoft: {
            clientId: process.env.MICROSOFT_CLIENT_ID as string,
            tenantId: process.env.MICROSOFT_TENANT_ID as string,
            clientAssertion: async () => getVercelOidcToken(),
        },
    },
})

You can also sign RFC 7523 assertions from a private key with the built-in helper:

auth.ts
import { betterAuth } from "better-auth"
import { createPrivateKeyJwtClientAssertionGetter } from "better-auth/oauth2"

export const auth = betterAuth({
    socialProviders: {
        microsoft: {
            clientId: process.env.MICROSOFT_CLIENT_ID as string,
            tenantId: process.env.MICROSOFT_TENANT_ID as string,
            clientAssertion: createPrivateKeyJwtClientAssertionGetter({
                privateKeyPem: process.env.MICROSOFT_PRIVATE_KEY_PEM as string,
                kid: process.env.MICROSOFT_PRIVATE_KEY_ID,
                algorithm: "RS256",
            }),
        },
    },
})

Entra does not emit the email claim for managed users by default, and the value is tenant-mutable and never verified by Microsoft; it must not be used for authorization decisions. Request email as an optional claim for managed users, and use profile.oid (plus profile.tid when correlating across tenants) as the stable identity anchor. See Handling Providers Without Email for the mapProfileToUser fallback.

Account identifiers

Better Auth uses the verified Microsoft Entra oid claim as the provider-owned account identifier. mapProfileToUser can map local user fields but cannot replace this identifier.

Upgrading from Better Auth 1.6 requires a one-time migration of existing Microsoft account rows from sub to oid. Follow Migrate Microsoft account identifiers before accepting production traffic on 1.7.

Large Profile Images ⚠️

Microsoft returns profile images as base64-encoded strings, which can exceed HTTP header size limits and cause request failures.

To work around this, use the mapProfileToUser function to either upload the image to your own storage or strip it entirely:

auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  socialProviders: {
    microsoft: {
      mapProfileToUser: (profile) => {
        const imgURL = uploadImageToStorage(profile.picture);

        return {
          image: imgURL, // or `null` to discard the image
        };
      },
    },
  },
});

Sign In with Microsoft

To sign in with Microsoft, you can use the signIn.social function provided by the client. The signIn function takes an object with the following properties:

  • provider: The provider to use. It should be set to microsoft.
auth-client.ts
import { createAuthClient } from "better-auth/client";

const authClient = createAuthClient();

const signIn = async () => {
  const data = await authClient.signIn.social({
    provider: "microsoft",
    callbackURL: "/dashboard", // The URL to redirect to after the sign in
  });
};

Preselecting an organizational domain

To skip the account picker for users you know belong to a given tenant or domain, forward Microsoft's domain_hint parameter on the call:

await authClient.signIn.social({
  provider: "microsoft",
  additionalParams: { domain_hint: "contoso.com" },
});