Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGoogle OAuth 2.0 / OIDC Emulator
Google OAuth 2.0 / OIDC模拟器
OAuth 2.0 and OpenID Connect emulation with authorization code flow, PKCE support, ID tokens, and OIDC discovery.
支持授权码流程、PKCE、ID令牌及OIDC发现的OAuth 2.0与OpenID Connect模拟工具。
Start
启动
bash
undefinedbash
undefinedGoogle only
仅针对Google
npx emulate --service google
npx emulate --service google
Default port
默认端口
Or programmatically:
```typescript
import { createEmulator } from 'emulate'
const google = await createEmulator({ service: 'google', port: 4002 })
// google.url === 'http://localhost:4002'
或以编程方式启动:
```typescript
import { createEmulator } from 'emulate'
const google = await createEmulator({ service: 'google', port: 4002 })
// google.url === 'http://localhost:4002'Pointing Your App at the Emulator
将应用指向模拟器
Environment Variable
环境变量
bash
GOOGLE_EMULATOR_URL=http://localhost:4002bash
GOOGLE_EMULATOR_URL=http://localhost:4002OAuth URL Mapping
OAuth URL映射
| Real Google URL | Emulator URL |
|---|---|
| |
| |
| |
| |
| |
| 真实Google URL | 模拟器URL |
|---|---|
| |
| |
| |
| |
| |
google-auth-library (Node.js)
google-auth-library(Node.js)
typescript
import { OAuth2Client } from 'google-auth-library'
const GOOGLE_URL = process.env.GOOGLE_EMULATOR_URL ?? 'https://accounts.google.com'
const client = new OAuth2Client({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: 'http://localhost:3000/api/auth/callback/google',
})
// Override the endpoints
const authorizeUrl = client.generateAuthUrl({
access_type: 'offline',
scope: ['openid', 'email', 'profile'],
})
// Replace the host in authorizeUrl with GOOGLE_URL, or construct manually:
const emulatorAuthorizeUrl = `${GOOGLE_URL}/o/oauth2/v2/auth?client_id=${process.env.GOOGLE_CLIENT_ID}&redirect_uri=...&scope=openid+email+profile&response_type=code&state=...`typescript
import { OAuth2Client } from 'google-auth-library'
const GOOGLE_URL = process.env.GOOGLE_EMULATOR_URL ?? 'https://accounts.google.com'
const client = new OAuth2Client({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: 'http://localhost:3000/api/auth/callback/google',
})
// 覆盖端点
const authorizeUrl = client.generateAuthUrl({
access_type: 'offline',
scope: ['openid', 'email', 'profile'],
})
// 将authorizeUrl中的主机替换为GOOGLE_URL,或手动构造:
const emulatorAuthorizeUrl = `${GOOGLE_URL}/o/oauth2/v2/auth?client_id=${process.env.GOOGLE_CLIENT_ID}&redirect_uri=...&scope=openid+email+profile&response_type=code&state=...`Auth.js / NextAuth.js
Auth.js / NextAuth.js
typescript
import Google from '@auth/core/providers/google'
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
url: `${process.env.GOOGLE_EMULATOR_URL}/o/oauth2/v2/auth`,
params: { scope: 'openid email profile' },
},
token: {
url: `${process.env.GOOGLE_EMULATOR_URL}/oauth2/token`,
},
userinfo: {
url: `${process.env.GOOGLE_EMULATOR_URL}/oauth2/v2/userinfo`,
},
})typescript
import Google from '@auth/core/providers/google'
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
url: `${process.env.GOOGLE_EMULATOR_URL}/o/oauth2/v2/auth`,
params: { scope: 'openid email profile' },
},
token: {
url: `${process.env.GOOGLE_EMULATOR_URL}/oauth2/token`,
},
userinfo: {
url: `${process.env.GOOGLE_EMULATOR_URL}/oauth2/v2/userinfo`,
},
})Passport.js
Passport.js
typescript
import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
const GOOGLE_URL = process.env.GOOGLE_EMULATOR_URL ?? 'https://accounts.google.com'
new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhost:3000/api/auth/callback/google',
authorizationURL: `${GOOGLE_URL}/o/oauth2/v2/auth`,
tokenURL: `${GOOGLE_URL}/oauth2/token`,
userProfileURL: `${GOOGLE_URL}/oauth2/v2/userinfo`,
}, verifyCallback)typescript
import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
const GOOGLE_URL = process.env.GOOGLE_EMULATOR_URL ?? 'https://accounts.google.com'
new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhost:3000/api/auth/callback/google',
authorizationURL: `${GOOGLE_URL}/o/oauth2/v2/auth`,
tokenURL: `${GOOGLE_URL}/oauth2/token`,
userProfileURL: `${GOOGLE_URL}/oauth2/v2/userinfo`,
}, verifyCallback)Seed Config
种子配置
yaml
google:
users:
- email: testuser@gmail.com
name: Test User
given_name: Test
family_name: User
picture: https://lh3.googleusercontent.com/a/default-user
email_verified: true
locale: en
- email: dev@example.com
name: Developer
oauth_clients:
- client_id: my-client-id.apps.googleusercontent.com
client_secret: GOCSPX-secret
name: My App
redirect_uris:
- http://localhost:3000/api/auth/callback/googleWhen no OAuth clients are configured, the emulator accepts any . With clients configured, strict validation is enforced for , , and .
client_idclient_idclient_secretredirect_uriyaml
google:
users:
- email: testuser@gmail.com
name: Test User
given_name: Test
family_name: User
picture: https://lh3.googleusercontent.com/a/default-user
email_verified: true
locale: en
- email: dev@example.com
name: Developer
oauth_clients:
- client_id: my-client-id.apps.googleusercontent.com
client_secret: GOCSPX-secret
name: My App
redirect_uris:
- http://localhost:3000/api/auth/callback/google当未配置OAuth客户端时,模拟器接受任意。配置客户端后,会对、和进行严格验证。
client_idclient_idclient_secretredirect_uriAPI Endpoints
API端点
OIDC Discovery
OIDC发现
bash
curl http://localhost:4002/.well-known/openid-configurationReturns the standard OIDC discovery document with all endpoints pointing to the emulator:
json
{
"issuer": "http://localhost:4002",
"authorization_endpoint": "http://localhost:4002/o/oauth2/v2/auth",
"token_endpoint": "http://localhost:4002/oauth2/token",
"userinfo_endpoint": "http://localhost:4002/oauth2/v2/userinfo",
"revocation_endpoint": "http://localhost:4002/oauth2/revoke",
"jwks_uri": "http://localhost:4002/oauth2/v3/certs",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["HS256"],
"scopes_supported": ["openid", "email", "profile"],
"code_challenge_methods_supported": ["plain", "S256"]
}bash
curl http://localhost:4002/.well-known/openid-configuration返回标准OIDC发现文档,所有端点均指向模拟器:
json
{
"issuer": "http://localhost:4002",
"authorization_endpoint": "http://localhost:4002/o/oauth2/v2/auth",
"token_endpoint": "http://localhost:4002/oauth2/token",
"userinfo_endpoint": "http://localhost:4002/oauth2/v2/userinfo",
"revocation_endpoint": "http://localhost:4002/oauth2/revoke",
"jwks_uri": "http://localhost:4002/oauth2/v3/certs",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["HS256"],
"scopes_supported": ["openid", "email", "profile"],
"code_challenge_methods_supported": ["plain", "S256"]
}JWKS
JWKS
bash
curl http://localhost:4002/oauth2/v3/certsReturns . ID tokens are signed with HS256 using an internal secret.
{ "keys": [] }bash
curl http://localhost:4002/oauth2/v3/certs返回。ID令牌使用内部密钥通过HS256算法签名。
{ "keys": [] }Authorization
授权
bash
undefinedbash
undefinedBrowser flow -- redirects to a user picker page
浏览器流程 -- 重定向到用户选择页面
curl -v "http://localhost:4002/o/oauth2/v2/auth?\
client_id=my-client-id.apps.googleusercontent.com&
redirect_uri=http://localhost:3000/api/auth/callback/google&\ scope=openid+email+profile&
response_type=code&
state=random-state&
nonce=random-nonce"
redirect_uri=http://localhost:3000/api/auth/callback/google&\ 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`) |
| `state` | Opaque state for CSRF protection |
| `nonce` | Nonce for ID token (optional) |
| `code_challenge` | PKCE challenge (optional) |
| `code_challenge_method` | `plain` or `S256` (optional) |
The emulator renders an HTML page where you select a seeded user. After selection, it redirects to `redirect_uri` with `?code=...&state=...`.curl -v "http://localhost:4002/o/oauth2/v2/auth?\
client_id=my-client-id.apps.googleusercontent.com&
redirect_uri=http://localhost:3000/api/auth/callback/google&\ scope=openid+email+profile&
response_type=code&
state=random-state&
nonce=random-nonce"
redirect_uri=http://localhost:3000/api/auth/callback/google&\ scope=openid+email+profile&
response_type=code&
state=random-state&
nonce=random-nonce"
查询参数:
| 参数 | 描述 |
|-------|-------------|
| `client_id` | OAuth客户端ID |
| `redirect_uri` | 回调URL |
| `scope` | 空格分隔的权限范围(`openid email profile`) |
| `state` | 用于CSRF保护的随机状态值 |
| `nonce` | ID令牌的随机值(可选) |
| `code_challenge` | PKCE挑战值(可选) |
| `code_challenge_method` | `plain`或`S256`(可选) |
模拟器会渲染一个HTML页面,供您选择已配置的种子用户。选择后,将重定向到`redirect_uri`,并携带`?code=...&state=...`参数。Token Exchange
令牌交换
bash
curl -X POST http://localhost:4002/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "code=<authorization_code>&\
client_id=my-client-id.apps.googleusercontent.com&\
client_secret=GOCSPX-secret&\
redirect_uri=http://localhost:3000/api/auth/callback/google&\
grant_type=authorization_code"Returns:
json
{
"access_token": "google_...",
"id_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile"
}The is a JWT (HS256) containing , , , , , , , , and optional .
id_tokensubemailemail_verifiednamegiven_namefamily_namepicturelocalenonceFor PKCE, include in the token request.
code_verifierbash
curl -X POST http://localhost:4002/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "code=<authorization_code>&\
client_id=my-client-id.apps.googleusercontent.com&\
client_secret=GOCSPX-secret&\
redirect_uri=http://localhost:3000/api/auth/callback/google&\
grant_type=authorization_code"返回结果:
json
{
"access_token": "google_...",
"id_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile"
}id_tokensubemailemail_verifiednamegiven_namefamily_namepicturelocalenonce若使用PKCE,需在令牌请求中包含参数。
code_verifierUser Info
用户信息
bash
curl http://localhost:4002/oauth2/v2/userinfo \
-H "Authorization: Bearer google_..."Returns:
json
{
"sub": "user-uid",
"email": "testuser@gmail.com",
"email_verified": true,
"name": "Test User",
"given_name": "Test",
"family_name": "User",
"picture": "https://lh3.googleusercontent.com/a/default-user",
"locale": "en"
}bash
curl http://localhost:4002/oauth2/v2/userinfo \
-H "Authorization: Bearer google_..."返回结果:
json
{
"sub": "user-uid",
"email": "testuser@gmail.com",
"email_verified": true,
"name": "Test User",
"given_name": "Test",
"family_name": "User",
"picture": "https://lh3.googleusercontent.com/a/default-user",
"locale": "en"
}Token Revocation
令牌吊销
bash
curl -X POST http://localhost:4002/oauth2/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=google_..."Returns . The token is removed from the emulator's token map.
200 OKbash
curl -X POST http://localhost:4002/oauth2/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=google_..."返回。该令牌将从模拟器的令牌映射中移除。
200 OKCommon Patterns
常见模式
Full Authorization Code Flow
完整授权码流程
bash
GOOGLE_URL="http://localhost:4002"
CLIENT_ID="my-client-id.apps.googleusercontent.com"
CLIENT_SECRET="GOCSPX-secret"
REDIRECT_URI="http://localhost:3000/api/auth/callback/google"bash
GOOGLE_URL="http://localhost:4002"
CLIENT_ID="my-client-id.apps.googleusercontent.com"
CLIENT_SECRET="GOCSPX-secret"
REDIRECT_URI="http://localhost:3000/api/auth/callback/google"1. Open in browser -- user picks a seeded account
1. 在浏览器中打开 -- 用户选择已配置的种子账户
$GOOGLE_URL/o/oauth2/v2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=openid+email+profile&response_type=code&state=abc
$GOOGLE_URL/o/oauth2/v2/auth?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 $GOOGLE_URL/oauth2/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 $GOOGLE_URL/oauth2/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 $GOOGLE_URL/oauth2/v2/userinfo
-H "Authorization: Bearer <access_token>"
-H "Authorization: Bearer <access_token>"
undefinedcurl $GOOGLE_URL/oauth2/v2/userinfo
-H "Authorization: Bearer <access_token>"
-H "Authorization: Bearer <access_token>"
undefinedPKCE Flow
PKCE流程
bash
undefinedbash
undefinedGenerate code_verifier and code_challenge
生成code_verifier和code_challenge
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 '+/' '-_')
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. 使用挑战值发起授权
$GOOGLE_URL/o/oauth2/v2/auth?...&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256
$GOOGLE_URL/o/oauth2/v2/auth?...&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256
2. Token exchange with verifier
2. 使用验证器交换令牌
curl -X POST $GOOGLE_URL/oauth2/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 $GOOGLE_URL/oauth2/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 (like ) can auto-configure from the discovery document:
openid-clienttypescript
import { Issuer } from 'openid-client'
const googleIssuer = await Issuer.discover(
process.env.GOOGLE_EMULATOR_URL ?? 'https://accounts.google.com'
)
const client = new googleIssuer.Client({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
redirect_uris: ['http://localhost:3000/api/auth/callback/google'],
})支持OIDC发现的库(如)可通过发现文档自动配置:
openid-clienttypescript
import { Issuer } from 'openid-client'
const googleIssuer = await Issuer.discover(
process.env.GOOGLE_EMULATOR_URL ?? 'https://accounts.google.com'
)
const client = new googleIssuer.Client({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
redirect_uris: ['http://localhost:3000/api/auth/callback/google'],
})