microsoft
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMicrosoft Entra ID Emulator
Microsoft Entra ID 模拟器
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 endpoint.
/v1.0/me模拟Microsoft Entra ID(Azure AD)v2.0 OAuth 2.0和OpenID Connect,支持授权码流程、PKCE、客户端凭证、RS256 ID令牌、OIDC发现,以及Microsoft Graph 接口。
/v1.0/meStart
启动
bash
undefinedbash
undefinedMicrosoft only
仅启动Microsoft服务
npx emulate --service microsoft
npx emulate --service microsoft
Default port (when run alone)
默认端口(单独运行时)
Or programmatically:
```typescript
import { createEmulator } from 'emulate'
const microsoft = await createEmulator({ service: 'microsoft', port: 4005 })
// microsoft.url === 'http://localhost:4005'
或者通过代码调用:
```typescript
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
环境变量
bash
MICROSOFT_EMULATOR_URL=http://localhost:4005bash
MICROSOFT_EMULATOR_URL=http://localhost:4005OAuth URL Mapping
OAuth URL映射
| Real Microsoft URL | Emulator URL |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| 真实Microsoft URL | 模拟器URL |
|---|---|
| |
| |
| |
| |
| |
| |
| |
Auth.js / NextAuth.js
Auth.js / NextAuth.js
typescript
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,
})typescript
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
Passport.js
typescript
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)typescript
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
MSAL.js
typescript
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)typescript
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
初始配置
yaml
microsoft:
users:
- email: testuser@outlook.com
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-36a304b66dadWhen no OAuth clients are configured, the emulator accepts any . With clients configured, strict validation is enforced for , , and .
client_idclient_idclient_secretredirect_uriyaml
microsoft:
users:
- email: testuser@outlook.com
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_idclient_idclient_secretredirect_uriAPI Endpoints
API 接口
OIDC Discovery
OIDC发现
bash
undefinedbash
undefinedDefault tenant
默认租户
Tenant-scoped (common, organizations, consumers, or specific tenant ID)
租户范围(common、organizations、consumers或特定租户ID)
Returns the standard OIDC discovery document:
```json
{
"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"]
}
返回标准OIDC发现文档:
```json
{
"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
JWKS
bash
curl http://localhost:4005/discovery/v2.0/keysReturns an RSA public key (: ) for verifying signatures.
kidemulate-microsoft-1id_tokenbash
curl http://localhost:4005/discovery/v2.0/keys返回用于验证签名的RSA公钥(: )。
id_tokenkidemulate-microsoft-1Authorization
授权
bash
undefinedbash
undefinedBrowser 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"
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) |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"
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令牌的随机串(可选) |
| `response_mode` | `query`(默认)或`form_post` |
| `code_challenge` | PKCE挑战值(可选) |
| `code_challenge_method` | `plain`或`S256`(可选) |Token Exchange
令牌交换
bash
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"Returns:
json
{
"access_token": "microsoft_...",
"refresh_token": "r_microsoft_...",
"id_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile"
}The is an RS256 JWT containing , , (tenant ID), , , , ("2.0"), and optional .
id_tokensuboidtidemailnamepreferred_usernamevernonceFor PKCE, include in the token request.
code_verifierSupports header with base64-encoded as an alternative to body parameters.
Authorization: Basicclient_id:client_secretbash
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"返回结果:
json
{
"access_token": "microsoft_...",
"refresh_token": "r_microsoft_...",
"id_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile"
}id_tokensuboidtidemailnamepreferred_usernamevernonce对于PKCE流程,需在令牌请求中包含。
code_verifier支持使用头,将进行base64编码后作为替代参数传递。
Authorization: Basicclient_id:client_secretClient Credentials
客户端凭证
bash
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 only (no or ).
access_tokenrefresh_tokenid_tokenbash
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_tokenrefresh_tokenid_tokenRefresh Token
刷新令牌
bash
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 , rotated , and new .
access_tokenrefresh_tokenid_tokenbash
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"返回新的、更新后的和新的。
access_tokenrefresh_tokenid_tokenUser Info
用户信息
bash
curl http://localhost:4005/oidc/userinfo \
-H "Authorization: Bearer microsoft_..."Returns:
json
{
"sub": "<oid>",
"email": "testuser@outlook.com",
"name": "Test User",
"given_name": "Test",
"family_name": "User",
"preferred_username": "testuser@outlook.com"
}bash
curl http://localhost:4005/oidc/userinfo \
-H "Authorization: Bearer microsoft_..."返回结果:
json
{
"sub": "<oid>",
"email": "testuser@outlook.com",
"name": "Test User",
"given_name": "Test",
"family_name": "User",
"preferred_username": "testuser@outlook.com"
}Microsoft Graph /me
Microsoft Graph /me
bash
curl http://localhost:4005/v1.0/me \
-H "Authorization: Bearer microsoft_..."Returns an OData-style response:
json
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
"displayName": "Test User",
"mail": "testuser@outlook.com",
"userPrincipalName": "testuser@outlook.com",
"id": "<oid>"
}bash
curl http://localhost:4005/v1.0/me \
-H "Authorization: Bearer microsoft_..."返回OData格式的响应:
json
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
"displayName": "Test User",
"mail": "testuser@outlook.com",
"userPrincipalName": "testuser@outlook.com",
"id": "<oid>"
}Logout
登出
bash
curl "http://localhost:4005/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000"Redirects to the if provided and valid.
post_logout_redirect_uribash
curl "http://localhost:4005/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000"如果提供了有效的,则重定向到该地址。
post_logout_redirect_uriToken Revocation
令牌撤销
bash
curl -X POST http://localhost:4005/oauth2/v2.0/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=microsoft_..."Returns . The token is removed from the emulator's token map.
200 OKbash
curl -X POST http://localhost:4005/oauth2/v2.0/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=microsoft_..."返回,该令牌会从模拟器的令牌映射中移除。
200 OKCommon Patterns
常用模式
Full Authorization Code Flow
完整授权码流程
bash
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"bash
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)
1. 在浏览器中打开(用户选择预配置的账户)
$MICROSOFT_URL/oauth2/v2.0/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=openid+email+profile&response_type=code&state=abc
$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:
2. 用户选择后,模拟器重定向到:
$REDIRECT_URI?code=<code>&state=abc
$REDIRECT_URI?code=<code>&state=abc
3. Exchange code for tokens
3. 交换授权码获取令牌
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"
-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"
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"
-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
4. 使用access_token获取用户信息
curl $MICROSOFT_URL/oidc/userinfo
-H "Authorization: Bearer <access_token>"
-H "Authorization: Bearer <access_token>"
undefinedcurl $MICROSOFT_URL/oidc/userinfo
-H "Authorization: Bearer <access_token>"
-H "Authorization: Bearer <access_token>"
undefinedPKCE Flow
PKCE流程
bash
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 '+/' '-_')bash
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
1. 携带挑战值发起授权请求
$MICROSOFT_URL/oauth2/v2.0/authorize?...&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256
$MICROSOFT_URL/oauth2/v2.0/authorize?...&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256
2. Token exchange with verifier
2. 携带验证值交换令牌
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"
-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"
undefinedcurl -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"
-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"
undefinedOIDC Discovery-Based Setup
基于OIDC发现的配置
Libraries that support OIDC discovery can auto-configure from the discovery document:
typescript
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'],
})支持OIDC发现的库可以通过发现文档自动配置:
typescript
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'],
})