mcpField
M
microsoft

microsoft

github.com/vercel-labs测试与质量2.0k180 次安装6 天前更新
代码仓库

关于 microsoft

Microsoft Entra ID Emulator 模拟 Microsoft Entra ID 的 OAuth 2.0 与 OpenID Connect 接口,面向需要测试身份验证流程的应用开发者。它支持多种授权流程、OIDC 发现及 Microsoft Graph 的 /v1.0/me 端点。

主要功能

  1. 01OAuth 2.0 授权码与客户端凭据流程
  2. 02PKCE 与刷新令牌
  3. 03RS256 ID 令牌及 JWKS
  4. 04OIDC 发现与用户信息端点
  5. 05Microsoft Graph /v1.0/me

适用场景

  1. 01本地测试 Entra ID 登录流程
  2. 02验证 PKCE 与令牌刷新
  3. 03测试 OIDC 客户端自动发现
  4. 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_idOAuth 客户端 ID
redirect_uri回调 URL
scope以空格分隔的范围(openid email profile User.Read)
state用于 CSRF 防护的不透明状态值
nonceID 令牌的 nonce(可选)
response_modequery(默认)或 form_post
code_challengePKCE 挑战值(可选)
code_challenge_methodplain 或 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。

评论

支持 Markdown