Labsco
vercel-labs logo

microsoft

1,500

by vercel · part of vercel-labs/emulate

Emulated Microsoft Entra ID (Azure AD) OAuth 2.0 / OpenID Connect for local development and testing. Use when the user needs to test Microsoft sign-in locally,…

🔥🔥🔥✓ VerifiedFreeNeeds API keys
🧰 Not standalone. This skill ships with vercel-labs/emulate and only works together with that tool — install the tool first, then add this skill.

Emulated Microsoft Entra ID (Azure AD) OAuth 2.0 / OpenID Connect for local development and testing. Use when the user needs to test Microsoft sign-in locally,…

Inspect the full instructions your agent will receiveExpand

This is the exact playbook injected into your agent when the skill activates — shown here so you can audit it before installing. You don't need to read it to use the skill.

by vercel

Emulated Microsoft Entra ID (Azure AD) OAuth 2.0 / OpenID Connect for local development and testing. Use when the user needs to test Microsoft sign-in locally,… npx skills add https://github.com/vercel-labs/emulate --skill microsoft Download ZIPGitHub1.5k

Microsoft Entra ID Emulator

Microsoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with authorization code flow, PKCE, client credentials, RS256 ID tokens, OIDC discovery, and a Microsoft Graph /v1.0/me endpoint.

Start

Copy & paste — that's it
# Microsoft only
npx emulate --service microsoft

# Default port (when run alone)
# http://localhost:4000

Or programmatically:

Copy & paste — that's it
import { createEmulator } from 'emulate'

const microsoft = await createEmulator({ service: 'microsoft', port: 4005 })
// microsoft.url === 'http://localhost:4005'

Pointing Your App at the Emulator

Environment Variable

Copy & paste — that's it
MICROSOFT_EMULATOR_URL=http://localhost:4005

OAuth URL Mapping

Real Microsoft URL Emulator URL https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration $MICROSOFT_EMULATOR_URL/{tenant}/v2.0/.well-known/openid-configuration https://login.microsoftonline.com/.well-known/openid-configuration $MICROSOFT_EMULATOR_URL/.well-known/openid-configuration https://login.microsoftonline.com/common/oauth2/v2.0/authorize $MICROSOFT_EMULATOR_URL/oauth2/v2.0/authorize https://login.microsoftonline.com/common/oauth2/v2.0/token $MICROSOFT_EMULATOR_URL/oauth2/v2.0/token https://login.microsoftonline.com/common/discovery/v2.0/keys $MICROSOFT_EMULATOR_URL/discovery/v2.0/keys https://graph.microsoft.com/oidc/userinfo $MICROSOFT_EMULATOR_URL/oidc/userinfo https://graph.microsoft.com/v1.0/me $MICROSOFT_EMULATOR_URL/v1.0/me

Auth.js / NextAuth.js

Copy & paste — that's it
import MicrosoftEntraId from '@auth/core/providers/microsoft-entra-id'

MicrosoftEntraId({
 clientId: process.env.MICROSOFT_CLIENT_ID,
 clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
 authorization: {
 url: `${process.env.MICROSOFT_EMULATOR_URL}/oauth2/v2.0/authorize`,
 params: { scope: 'openid email profile User.Read' },
 },
 token: {
 url: `${process.env.MICROSOFT_EMULATOR_URL}/oauth2/v2.0/token`,
 },
 userinfo: {
 url: `${process.env.MICROSOFT_EMULATOR_URL}/oidc/userinfo`,
 },
 issuer: process.env.MICROSOFT_EMULATOR_URL,
})

Passport.js

Copy & paste — that's it
import { OIDCStrategy } from 'passport-azure-ad'

const MICROSOFT_URL = process.env.MICROSOFT_EMULATOR_URL ?? 'https://login.microsoftonline.com'

new OIDCStrategy({
 identityMetadata: `${MICROSOFT_URL}/.well-known/openid-configuration`,
 clientID: process.env.MICROSOFT_CLIENT_ID,
 clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
 redirectUrl: 'http://localhost:3000/api/auth/callback/microsoft-entra-id',
 responseType: 'code',
 responseMode: 'query',
 scope: ['openid', 'email', 'profile'],
}, verifyCallback)

MSAL.js

Copy & paste — that's it
import { ConfidentialClientApplication } from '@azure/msal-node'

const msalConfig = {
 auth: {
 clientId: process.env.MICROSOFT_CLIENT_ID,
 clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
 authority: process.env.MICROSOFT_EMULATOR_URL,
 knownAuthorities: [process.env.MICROSOFT_EMULATOR_URL],
 },
}

const cca = new ConfidentialClientApplication(msalConfig)

Seed Config

Copy & paste — that's it
microsoft:
 users:
 - email: [email protected]
 name: Test User
 given_name: Test
 family_name: User
 tenant_id: 9188040d-6c67-4c5b-b112-36a304b66dad
 oauth_clients:
 - client_id: example-client-id
 client_secret: example-client-secret
 name: My Microsoft App
 redirect_uris:
 - http://localhost:3000/api/auth/callback/microsoft-entra-id
 tenant_id: 9188040d-6c67-4c5b-b112-36a304b66dad

When no OAuth clients are configured, the emulator accepts any client_id. With clients configured, strict validation is enforced for client_id, client_secret, and redirect_uri.

API Endpoints

OIDC Discovery

Copy & paste — that's it
# Default tenant
curl http://localhost:4005/.well-known/openid-configuration

# Tenant-scoped (common, organizations, consumers, or specific tenant ID)
curl http://localhost:4005/common/v2.0/.well-known/openid-configuration

Returns the standard OIDC discovery document:

Copy & paste — that's it
{
 "issuer": "http://localhost:4005/{tenant}/v2.0",
 "authorization_endpoint": "http://localhost:4005/oauth2/v2.0/authorize",
 "token_endpoint": "http://localhost:4005/oauth2/v2.0/token",
 "userinfo_endpoint": "http://localhost:4005/oidc/userinfo",
 "end_session_endpoint": "http://localhost:4005/oauth2/v2.0/logout",
 "jwks_uri": "http://localhost:4005/discovery/v2.0/keys",
 "response_types_supported": ["code"],
 "subject_types_supported": ["pairwise"],
 "id_token_signing_alg_values_supported": ["RS256"],
 "scopes_supported": ["openid", "email", "profile", "User.Read", "offline_access"],
 "token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"]
}

JWKS

Copy & paste — that's it
curl http://localhost:4005/discovery/v2.0/keys

Returns an RSA public key (kid: emulate-microsoft-1) for verifying id_token signatures.

Authorization

Copy & paste — that's it
# Browser flow: redirects to a user picker page
curl -v "http://localhost:4005/oauth2/v2.0/authorize?\
client_id=example-client-id&\
redirect_uri=http://localhost:3000/api/auth/callback/microsoft-entra-id&\
scope=openid+email+profile&\
response_type=code&\
state=random-state&\
nonce=random-nonce"

Query parameters:

Param Description client_id OAuth client ID redirect_uri Callback URL scope Space-separated scopes (openid email profile User.Read) state Opaque state for CSRF protection nonce Nonce for ID token (optional) response_mode query (default) or form_post code_challenge PKCE challenge (optional) code_challenge_method plain or S256 (optional)

Token Exchange

Copy & paste — that's it
curl -X POST http://localhost:4005/oauth2/v2.0/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "code= &\
client_id=example-client-id&\
client_secret=example-client-secret&\
redirect_uri=http://localhost:3000/api/auth/callback/microsoft-entra-id&\
grant_type=authorization_code"

Returns:

Copy & paste — that's it
{
 "access_token": "microsoft_...",
 "refresh_token": "r_microsoft_...",
 "id_token": " ",
 "token_type": "Bearer",
 "expires_in": 3600,
 "scope": "openid email profile"
}

The id_token is an RS256 JWT containing sub, oid, tid (tenant ID), email, name, preferred_username, ver ("2.0"), and optional nonce.

For PKCE, include code_verifier in the token request.

Supports Authorization: Basic header with base64-encoded client_id:client_secret as an alternative to body parameters.

Client Credentials

Copy & paste — that's it
curl -X POST http://localhost:4005/oauth2/v2.0/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "client_id=example-client-id&\
client_secret=example-client-secret&\
grant_type=client_credentials&\
scope=https://graph.microsoft.com/.default"

Returns an access_token only (no refresh_token or id_token).

Refresh Token

Copy & paste — that's it
curl -X POST http://localhost:4005/oauth2/v2.0/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "refresh_token=r_microsoft_...&\
client_id=example-client-id&\
grant_type=refresh_token"

Returns a new access_token, rotated refresh_token, and new id_token.

User Info

Copy & paste — that's it
curl http://localhost:4005/oidc/userinfo \
 -H "Authorization: Bearer microsoft_..."

Returns:

Copy & paste — that's it
{
 "sub": " ",
 "email": "[email protected]",
 "name": "Test User",
 "given_name": "Test",
 "family_name": "User",
 "preferred_username": "[email protected]"
}

Microsoft Graph /me

Copy & paste — that's it
curl http://localhost:4005/v1.0/me \
 -H "Authorization: Bearer microsoft_..."

Returns an OData-style response:

Copy & paste — that's it
{
 "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
 "displayName": "Test User",
 "mail": "[email protected]",
 "userPrincipalName": "[email protected]",
 "id": " "
}

Logout

Copy & paste — that's it
curl "http://localhost:4005/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000"

Redirects to the post_logout_redirect_uri if provided and valid.

Token Revocation

Copy & paste — that's it
curl -X POST http://localhost:4005/oauth2/v2.0/revoke \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "token=microsoft_..."

Returns 200 OK. The token is removed from the emulator's token map.

Common Patterns

Full Authorization Code Flow

Copy & paste — that's it
MICROSOFT_URL="http://localhost:4005"
CLIENT_ID="example-client-id"
CLIENT_SECRET="example-client-secret"
REDIRECT_URI="http://localhost:3000/api/auth/callback/microsoft-entra-id"

# 1. Open in browser (user picks a seeded account)
# $MICROSOFT_URL/oauth2/v2.0/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=openid+email+profile&response_type=code&state=abc

# 2. After user selection, emulator redirects to:
# $REDIRECT_URI?code= &state=abc

# 3. Exchange code for tokens
curl -X POST $MICROSOFT_URL/oauth2/v2.0/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "code= &client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&redirect_uri=$REDIRECT_URI&grant_type=authorization_code"

# 4. Fetch user info with the access_token
curl $MICROSOFT_URL/oidc/userinfo \
 -H "Authorization: Bearer "

PKCE Flow

Copy & paste — that's it
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-43)
CODE_CHALLENGE=$(echo -n $CODE_VERIFIER | openssl dgst -sha256 -binary | base64 | tr -d '=' | tr '+/' '-_')

# 1. Authorize with challenge
# $MICROSOFT_URL/oauth2/v2.0/authorize?...&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256

# 2. Token exchange with verifier
curl -X POST $MICROSOFT_URL/oauth2/v2.0/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "code= &client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&redirect_uri=$REDIRECT_URI&grant_type=authorization_code&code_verifier=$CODE_VERIFIER"

OIDC Discovery-Based Setup

Libraries that support OIDC discovery can auto-configure from the discovery document:

Copy & paste — that's it
import { Issuer } from 'openid-client'

const microsoftIssuer = await Issuer.discover(
 process.env.MICROSOFT_EMULATOR_URL ?? 'https://login.microsoftonline.com/common/v2.0'
)

const client = new microsoftIssuer.Client({
 client_id: process.env.MICROSOFT_CLIENT_ID,
 client_secret: process.env.MICROSOFT_CLIENT_SECRET,
 redirect_uris: ['http://localhost:3000/api/auth/callback/microsoft-entra-id'],
})