microsoft

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Microsoft 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
/v1.0/me
endpoint.
模拟Microsoft Entra ID(Azure AD)v2.0 OAuth 2.0和OpenID Connect,支持授权码流程、PKCE、客户端凭证、RS256 ID令牌、OIDC发现,以及Microsoft Graph
/v1.0/me
接口。

Start

启动

bash
undefined
bash
undefined

Microsoft 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:4005
bash
MICROSOFT_EMULATOR_URL=http://localhost:4005

OAuth URL Mapping

OAuth URL映射

Real Microsoft URLEmulator 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
真实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

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-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
.
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-36a304b66dad
当未配置OAuth客户端时,模拟器接受任意
client_id
。配置客户端后,会严格验证
client_id
client_secret
redirect_uri

API Endpoints

API 接口

OIDC Discovery

OIDC发现

bash
undefined
bash
undefined

Default 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/keys
Returns an RSA public key (
kid
:
emulate-microsoft-1
) for verifying
id_token
signatures.
bash
curl http://localhost:4005/discovery/v2.0/keys
返回用于验证
id_token
签名的RSA公钥(
kid
:
emulate-microsoft-1
)。

Authorization

授权

bash
undefined
bash
undefined

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) |
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令牌的随机串(可选) |
| `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
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.
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"
返回结果:
json
{
  "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
支持使用
Authorization: Basic
头,将
client_id:client_secret
进行base64编码后作为替代参数传递。

Client 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
access_token
only (no
refresh_token
or
id_token
).
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"
仅返回
access_token
(无
refresh_token
id_token
)。

Refresh 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
access_token
, rotated
refresh_token
, and new
id_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"
返回新的
access_token
、更新后的
refresh_token
和新的
id_token

User 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
post_logout_redirect_uri
if provided and valid.
bash
curl "http://localhost:4005/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000"
如果提供了有效的
post_logout_redirect_uri
,则重定向到该地址。

Token Revocation

令牌撤销

bash
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.
bash
curl -X POST http://localhost:4005/oauth2/v2.0/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=microsoft_..."
返回
200 OK
,该令牌会从模拟器的令牌映射中移除。

Common 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"
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

4. 使用access_token获取用户信息

curl $MICROSOFT_URL/oidc/userinfo
-H "Authorization: Bearer <access_token>"
undefined
curl $MICROSOFT_URL/oidc/userinfo
-H "Authorization: Bearer <access_token>"
undefined

PKCE 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"
undefined
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"
undefined

OIDC 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'],
})