google

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Google 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
undefined
bash
undefined

Google 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:4002
bash
GOOGLE_EMULATOR_URL=http://localhost:4002

OAuth URL Mapping

OAuth URL映射

Real Google URLEmulator URL
https://accounts.google.com/o/oauth2/v2/auth
$GOOGLE_EMULATOR_URL/o/oauth2/v2/auth
https://oauth2.googleapis.com/token
$GOOGLE_EMULATOR_URL/oauth2/token
https://www.googleapis.com/oauth2/v2/userinfo
$GOOGLE_EMULATOR_URL/oauth2/v2/userinfo
https://accounts.google.com/.well-known/openid-configuration
$GOOGLE_EMULATOR_URL/.well-known/openid-configuration
https://www.googleapis.com/oauth2/v3/certs
$GOOGLE_EMULATOR_URL/oauth2/v3/certs
真实Google URL模拟器URL
https://accounts.google.com/o/oauth2/v2/auth
$GOOGLE_EMULATOR_URL/o/oauth2/v2/auth
https://oauth2.googleapis.com/token
$GOOGLE_EMULATOR_URL/oauth2/token
https://www.googleapis.com/oauth2/v2/userinfo
$GOOGLE_EMULATOR_URL/oauth2/v2/userinfo
https://accounts.google.com/.well-known/openid-configuration
$GOOGLE_EMULATOR_URL/.well-known/openid-configuration
https://www.googleapis.com/oauth2/v3/certs
$GOOGLE_EMULATOR_URL/oauth2/v3/certs

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/google
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
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_id
。配置客户端后,会对
client_id
client_secret
redirect_uri
进行严格验证。

API Endpoints

API端点

OIDC Discovery

OIDC发现

bash
curl http://localhost:4002/.well-known/openid-configuration
Returns 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/certs
Returns
{ "keys": [] }
. ID tokens are signed with HS256 using an internal secret.
bash
curl http://localhost:4002/oauth2/v3/certs
返回
{ "keys": [] }
。ID令牌使用内部密钥通过HS256算法签名。

Authorization

授权

bash
undefined
bash
undefined

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

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"

查询参数:

| 参数 | 描述 |
|-------|-------------|
| `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
id_token
is a JWT (HS256) containing
sub
,
email
,
email_verified
,
name
,
given_name
,
family_name
,
picture
,
locale
, and optional
nonce
.
For PKCE, include
code_verifier
in the token request.
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"
返回结果:
json
{
  "access_token": "google_...",
  "id_token": "<jwt>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid email profile"
}
id_token
是一个使用HS256算法签名的JWT,包含
sub
email
email_verified
name
given_name
family_name
picture
locale
及可选的
nonce
字段。
若使用PKCE,需在令牌请求中包含
code_verifier
参数。

User 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
200 OK
. The token is removed from the emulator's token map.
bash
curl -X POST http://localhost:4002/oauth2/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=google_..."
返回
200 OK
。该令牌将从模拟器的令牌映射中移除。

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

4. Fetch user info with the access_token

4. 使用access_token获取用户信息

curl $GOOGLE_URL/oauth2/v2/userinfo
-H "Authorization: Bearer <access_token>"
undefined
curl $GOOGLE_URL/oauth2/v2/userinfo
-H "Authorization: Bearer <access_token>"
undefined

PKCE Flow

PKCE流程

bash
undefined
bash
undefined

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

OIDC Discovery-Based Setup

基于OIDC发现的配置

Libraries that support OIDC discovery (like
openid-client
) can auto-configure from the discovery document:
typescript
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-client
)可通过发现文档自动配置:
typescript
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'],
})