关于 microsoft
Microsoft Entra ID Emulator 模拟 Microsoft Entra ID 的 OAuth 2.0 与 OpenID Connect 接口,面向需要测试身份验证流程的应用开发者。它支持多种授权流程、OIDC 发现及 Microsoft Graph 的 /v1.0/me 端点。
主要功能
- 01OAuth 2.0 授权码与客户端凭据流程
- 02PKCE 与刷新令牌
- 03RS256 ID 令牌及 JWKS
- 04OIDC 发现与用户信息端点
- 05Microsoft Graph /v1.0/me
适用场景
- 01本地测试 Entra ID 登录流程
- 02验证 PKCE 与令牌刷新
- 03测试 OIDC 客户端自动发现
- 04模拟 Graph 用户资料请求
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vercel-labs/ emulate --skill microsoft安装后在 Claude Code 中输入/microsoft确认已加载。
详细介绍
Microsoft Entra ID 模拟器
Microsoft Entra ID(Azure AD)v2.0 OAuth 2.0 和 OpenID Connect 模拟,支持授权码流程、PKCE、客户端凭据、RS256 ID 令牌、OIDC 发现,以及 Microsoft Graph /v1.0/me 端点。
启动
默认情况下,本地监听器绑定到 127.0.0.1。若要从容器或其他计算机访问,请向 CLI 传入 --host 0.0.0.0,或向 createEmulator 传入 hostname: '0.0.0.0'。配置 --base-url 或 baseUrl,以设置这些客户端可访问的对外公布 URL。
# Microsoft only
npx emulate --service microsoft
# Default port (when run alone)
# http://localhost:4000
也可以通过编程方式启动:
import { createEmulator } from 'emulate'
const microsoft = await createEmulator({ service: 'microsoft', port: 4005 })
// microsoft.url === 'http://localhost:4005'
将应用指向模拟器
环境变量
MICROSOFT_EMULATOR_URL=http://localhost:4005
OAuth URL 映射
| 真实的 Microsoft URL | 模拟器 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
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
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
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)
种子配置
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
未配置 OAuth 客户端时,模拟器接受任意 client_id。配置了客户端后,将对 client_id、client_secret 和 redirect_uri 进行严格验证。
API 端点
OIDC 发现
# 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
返回标准的 OIDC 发现文档:
{
"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
curl http://localhost:4005/discovery/v2.0/keys
返回一个 RSA 公钥(kid:emulate-microsoft-1),用于验证 id_token 签名。
授权
# 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"
查询参数:
| 参数 | 说明 |
|---|---|
client_id | OAuth 客户端 ID |
redirect_uri | 回调 URL |
scope | 以空格分隔的范围(openid email profile User.Read) |
state | 用于 CSRF 防护的不透明状态值 |
nonce | ID 令牌的 nonce(可选) |
response_mode | query(默认)或 form_post |
code_challenge | PKCE 挑战值(可选) |
code_challenge_method | plain 或 S256(可选) |
令牌交换
curl -X POST http://localhost:4005/oauth2/v2.0/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "code=<authorization_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"
返回:
{
"access_token": "microsoft_...",
"refresh_token": "r_microsoft_...",
"id_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile"
}
id_token 是一个 RS256 JWT,包含 sub、oid、tid(租户 ID)、email、name、preferred_username、ver("2.0")以及可选的 nonce。
使用 PKCE 时,请在令牌请求中包含 code_verifier。
支持将经过 base64 编码的 client_id:client_secret 放在 Authorization: Basic 标头中,以代替请求正文参数。
客户端凭据
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"
仅返回 access_token(不返回 refresh_token 或 id_token)。
刷新令牌
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&\
client_secret=example-client-secret&\
grant_type=refresh_token"
返回新的 access_token、轮换后的 refresh_token 和新的 id_token。提交请求的 client_id 和 client_secret 必须属于获得该刷新令牌的客户端。仍支持未存储客户端绑定信息的旧版刷新记录。
用户信息
curl http://localhost:4005/oidc/userinfo \
-H "Authorization: Bearer microsoft_..."
返回:
{
"sub": "<oid>",
"email": "[email protected]",
"name": "Test User",
"given_name": "Test",
"family_name": "User",
"preferred_username": "[email protected]"
}
Microsoft Graph /me
curl http://localhost:4005/v1.0/me \
-H "Authorization: Bearer microsoft_..."
返回 OData 风格的响应:
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
"displayName": "Test User",
"mail": "[email protected]",
"userPrincipalName": "[email protected]",
"id": "<oid>"
}
注销
curl "http://localhost:4005/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000"
如果提供了有效的 post_logout_redirect_uri,则重定向到该 URI。
令牌撤销
curl -X POST http://localhost:4005/oauth2/v2.0/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=microsoft_..."
返回 200 OK。该令牌会从模拟器的令牌映射中移除。
常见模式
完整授权码流程
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=<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=<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 <access_token>"
PKCE 流程
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=<code>&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&redirect_uri=$REDIRECT_URI&grant_type=authorization_code&code_verifier=$CODE_VERIFIER"
基于 OIDC 发现的设置
支持 OIDC 发现的库可以从发现文档自动配置:
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'],
})
与内置模拟器并用自定义模拟器
使用 npx emulate init --custom inventory 搭建第三方 API 模拟器并进行测试。使用来自 emulate 的 defineConfig 在 emulate.config.ts 中注册该模拟器,并与 { emulator: "microsoft" } 等内置条目并列。运行 npx emulate start --watch 可重新加载导入内容,并在打印出的 /_emulate URL 中检查自定义状态。成功重新加载后,会将本次运行重置为种子状态。现有的扁平种子配置仍然有效;--config 用于指定文件。有关编写和测试第三方 API 模拟器的信息,请参阅 https://emulate.dev/docs/custom-emulators。