OKX OAuth2.0 Web 集成(OKX 第三方登录)
文档写的云里雾里,不深入了解 很难搞清楚 okx 几个登录之间的关系。
自己整理了一张图(如有错漏,欢迎指正):
下面是关于 OKX OAuth 2.0 集成的关键点整理,以及 Web 端使用 PKCE 模式 和 授权码模式 的详细流程整理:
一、OKX OAuth 2.0 集成关键点
-
功能和特点
- 基于 OAuth 2.0 协议(RFC 6749)以及部分 OAuth 2.1 草案新特性。
- 用户无需提供 API Key 或登录密码,只需授权即可完成交易。
- 支持 Web 和 移动应用 两种接入场景。
-
授权模式
- 授权码模式:
- 适用于有服务器的应用,需存储
client_secret
。 - 更适合服务器与 OKX OAuth 服务端进行密钥交互。
- 适用于有服务器的应用,需存储
- PKCE 模式:
- 适用于无服务器或不愿后端参与授权的场景。
- 使用临时密钥
code_verifier
,无需存储client_secret
。
- 授权码模式:
-
令牌机制
- 访问令牌(Access Token):有效期 1 小时,用于调用 API。
- 刷新令牌(Refresh Token):有效期 3 天,用于刷新访问令牌。
- 当刷新令牌失效后,需重新授权获取新令牌。
-
安全性措施
- PKCE 模式需绑定设备号(
term_id
),防止令牌被盗用。 - 授权码模式需校验 IP 地址是否在白名单内。
- PKCE 模式需绑定设备号(
二、授权码流程
1. 第三方应用发起授权
调用 SDK API 发起授权接口。
const { OKEXOAuthSDK } = window;
if (OKEXOAuthSDK) {
OKEXOAuthSDK.authorize({
response_type: 'code',
access_type: 'offline',
client_id: 'YOUR_CLIENT_ID',
redirect_uri: encodeURIComponent("https://example.com/oauth/callback"),
scope: 'read_only,trade',
state,
channelId: "xxxxxxxx"
});
} else {
console.error('sdk has not been loaded');
}
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
response_type | String | 否 | 必须为 code ,不传默认 code |
access_type | String | 是 | 必须为 offline |
client_id | String | 是 | 第三方应用 client_id |
redirect_uri | String | 是 | 回调地址,即授权成功后的跳转地址。需要做 URL 编码。 |
如果不在注册的跳转地址白名单中,则会授权失败。 | |||
scope | String | 是 | 授权范围。多个权限用半角逗号分隔:read_only (只读)或 trade (交易) |
state | String | 是 | 随机生成的一串字符,用于防止 CSRF 攻击。可通过生成 State 接口获取 |
code_challenge | String | 否 | code_verifier 经过 hash 和 base64 urlencode 处理后的字符 |
code_challenge_method | String | 否 | 必须为 s256 ,授权码模式下,该字段非必填 |
2. 用户授权
第三方应用发起授权后,会跳转到 OKX 授权页面,用户可以选择同意授权或者拒绝授权,并可以调整授权范围。
3. 跳转回第三方应用
用户同意授权后,会跳转到发起授权请求时指定的回调地址。授权码和 state
会以参数方式一起返回。
返回示例
GET https://example.com/oauth/callback?code=f8d24cf76b5d41ccafeb646be65e383965TtKv&state=4YrCgpWQrsD8jdnv
4. 用授权码换取令牌
第三方应用拿到上一步获取的授权码,第三方后端服务调用 REST API 获取令牌。
POST /v5/users/oauth/token
body {
"grant_type": "authorization_code",
"code": "f8d24cf76b5d41ccafeb646be65e383965TtKv",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
grant_type | String | 是 | authorization_code :获取访问令牌 |
client_id | String | 是 | 第三方应用 client_id |
client_secret | String | 是 | 第三方应用 client_secret |
code | String | 是 | 发起授权请求成功后获得的授权码 |
code_verifier | String | 可选 | 初始请求中生成的 code_verifier ,对应于发起授权请求中的 code_challenge 参数 |
term_id | String | 否 | 设备号,用于绑定令牌,可通过 SDK API 生成设备号接口获取 |
成功返回示例
{
"access_token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg1MjUyNjA2MzI0MzM1ODE5NkJDQkFFMkQ3a0hrUiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODUyNTI2MCwiZXhwIjoxNjM4NTI4ODYwLCJzdWIiOiIxMC4yNTQuMjcuMTIwIiwiYW95IjoiOSIsInZlciI6IjEiLCJkZXYiOiIxMjM0NTY3ODkwMTIzNDU2NyIsImd0eSI6ImF1dGhvcml6ZSJ9.u0mnMizduK1zGBY9Ysej2OZWCRpdMzLf7JXehZUDYxIWOCQo6wOX-rPk7QtR9TfYN-Rg64cUkjXbIquwN8rNKg",
"refresh_token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg1MjUyNjA2MzI0MzM1ODE5NkJDQkFFMkQ3a0hrUiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODUyNTI2MCwiZXhwIjoxNjM4Nzg0NDYwLCJzdWIiOiIxMC4yNTQuMjcuMTIwIiwiYW95IjoiOSIsInZlciI6IjEiLCJkZXYiOiIxMjM0NTY3ODkwMTIzNDU2NyIsImd0eSI6InJlZnJlc2gifQ.CyD4NMAzuBAiTPN58xzLQVUwOnFEKaXIRUQacx77kHZQESHRqH83b8SEiFGKkprDlcguMn1A9fHIzPrulFCsnw",
"token_type": "bearer",
"expires_in": 3600
}
5. 刷新令牌
参看 REST API 刷新令牌接口。
6. 撤销令牌
参看 REST API 撤销令牌接口。
三、PKCE 流程
1. 第三方应用发起授权
调用 SDK API 发起授权接口。
const { OKEXOAuthSDK } = window;
if (OKEXOAuthSDK) {
OKEXOAuthSDK.authorize({
response_type: 'code',
access_type: 'online',
client_id: 'YOUR_CLIENT_ID',
redirect_uri: encodeURIComponent("https://example.com/oauth/callback"),
scope: 'read_only,trade',
code_challenge: '198ba82d6817cc0d42b23cf5e90262c8a3976b90a444e802f341593d784fb89d',
code_challenge_method: 's256',
state
});
} else {
console.error('sdk has not been loaded');
}
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
response_type | String | 否 | 必须为 code ,不传默认 code |
access_type | String | 是 | 必须为 online |
client_id | String | 是 | 第三方应用 client_id |
redirect_uri | String | 是 | 回调地址,即授权成功后的跳转地址。需要做 URL 编码。 |
如果不在注册的跳转地址白名单中,则会授权失败。 | |||
scope | String | 是 | 授权范围。多个权限用半角逗号分隔:read_only (只读)或 trade (交易) |
state | String | 是 | 随机生成的一串字符,用于防止 CSRF 攻击。可通过 SDK API 生成 State 接口获取 |
code_challenge | String | 是 | code_verifier 经过 hash 和 base64 urlencode 处理后的字符 |
code_challenge_method | String | 是 | 必须为 s256 ,PKCE 模式下,该字段必填 |
2. 用户授权
第三方应用发起授权后,会跳转到 OKX 授权页面,用户可以选择同意授权或者拒绝授权,并可以调整授权范围。
3. 跳转回第三方应用
用户同意授权后,会跳转到发起授权请求时指定的回调地址。授权码和 state
会以参数方式一起返回。
返回示例
GET https://example.com/oauth/callback?code=f8d24cf76b5d41ccafeb646be65e383965TtKv&state=4YrCgpWQrsD8jdnv
4. 用授权码换取令牌
第三方应用拿到上一步获取的授权码,第三方 WEB 应用调用 SDK API 获取令牌。
OKEXOAuthSDK.requestToken({
grant_type: 'authorization_code',
client_id: 'YOUR_CLIENT_ID',
code_verifier: 'YOUR_CODE_VERIFIER',
code,
term_id: termId
}).then((res) => {
const token = res.access_token;
console.log(`获取 token 成功:${token}`)
}).catch((err) => {
console.error(err?.msg);
});
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
grant_type | String | 是 | authorization_code :获取访问令牌 |
client_id | String | 是 | 第三方应用 client_id |
code_verifier | String | 是 | 初始请求中生成的 code_verifier ,对应于发起授权请求中的 code_challenge 参数 |
code | String | 是 | 发起授权请求成功后获得的授权码 |
term_id | String | 是 | 设备号,用于绑定令牌,可通过 SDK API 生成设备号接口获取 |
成功返回示例
{
"access_token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg1MjUyNjA2MzI0MzM1ODE5NkJDQkFFMkQ3a0hrUiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODUyNTI2MCwiZXhwIjoxNjM4NTI4ODYwLCJzdWIiOiIxMC4yNTQuMjcuMTIwIiwiYW95IjoiOSIsInZlciI6IjEiLCJkZXYiOiIxMjM0NTY3ODkwMTIzNDU2NyIsImd0eSI6ImF1dGhvcml6ZSJ9.u0mnMizduK1zGBY9Ysej2OZWCRpdMzLf7JXehZUDYxIWOCQo6wOX-rPk7QtR9TfYN-Rg64cUkjXbIquwN8rNKg",
"refresh_token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg1MjUyNjA2MzI0MzM1ODE5NkJDQkFFMkQ3a0hrUiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODUyNTI2MCwiZXhwIjoxNjM4Nzg0NDYwLCJzdWIiOiIxMC4yNTQuMjcuMTIwIiwiYW95IjoiOSIsInZlciI6IjEiLCJkZXYiOiIxMjM0NTY3ODkwMTIzNDU2NyIsImd0eSI6InJlZnJlc2gifQ.CyD4NMAzuBAiTPN58xzLQVUwOnFEKaXIRUQacx77kHZQESHRqH83b8SEiFGKkprDlcguMn1A9fHIzPrulFCsnw",
"token_type": "bearer",
"expires_in": 3600
}
5. 刷新令牌
参看 SDK API 刷新令牌接口。
6. 撤销令牌
参看 SDK API 撤销令牌接口。
四、附录 REST API
- 刷新令牌:访问令牌过期后使用刷新令牌获取新令牌。
- 撤销令牌:可以通过 API 或 SDK 主动撤销令牌。
一、授权码模式令牌请求
1. 获取令牌(授权码模式)
请求示例
POST /v5/users/oauth/token
body {
"grant_type": "authorization_code",
"code": "f8d24cf76b5d41ccafeb646be65e383965TtKv",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
grant_type | String | 是 | authorization_code: 获取访问令牌 |
client_id | String | 是 | 第三方应用 client_id |
client_secret | String | 是 | 第三方应用 client_secret |
code | String | 是 | 发起授权请求成功后获得的授权码 |
code_verifier | String | 可选 | 初始请求中生成的 code verifier,对应于发起授权请求中的 code_challenge 参数 |
term_id | String | 否 | 设备号,用于绑定令牌,可通过生成设备号接口获取 |
返回参数
参数名 | 参数类型 | 描述 |
---|---|---|
access_token | String | 访问令牌 |
refresh_token | String | 刷新令牌 |
token_type | String | Bearer |
expires_in | Number | 访问令牌有效期,单位为秒 |
返回示例
{
"access_token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg1MjUyNjA2MzI0MzM1ODE5NkJDQkFFMkQ3a0hrUiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODUyNTI2MCwiZXhwIjoxNjM4NTI4ODYwLCJzdWIiOiIxMC4yNTQuM...
"refresh_token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg1MjUyNjA2MzI0MzM1ODE5NkJDQkFFMkQ3a0hrUiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODUyNTI2MCwiZXhwIjoxNjM4Nzg0NDYwLCJzdWIiOiIxMC4yNTQuM...
"token_type": "bearer",
"expires_in": 3600
}
2. 刷新令牌(授权码模式)
请求示例
POST /v5/users/oauth/token
body {
"grant_type": "refresh_token",
"refresh_token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg0MzU4NTk3MDc2MTFERjk0OEZFNUNFQTUwZUlURiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODQzNTg1OSwiZXhwIjoxNjM4Njk1MDU5LCJzdWIiOiIxMC4yNTQuMjcuMTIwIiwiYW95IjoiOSIsInZlciI6IjEiLCJndHkiOiJyZWZyZXNoIn0.Kfs0jBfbfsrnc4HE44ShXNxp2TuHio5R8GpBebEx5rALA_BTgqBtJRwC6330TdU9J6kOZiNp97Zd8RNPJYjXHw",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
grant_type | String | 是 | refresh_token: 刷新访问令牌 |
client_id | String | 是 | 第三方应用 client_id |
client_secret | String | 是 | 第三方应用 client_secret |
refresh_token | String | 是 | 刷新令牌 |
返回参数
参数名 | 参数类型 | 描述 |
---|---|---|
access_token | String | 访问令牌 |
refresh_token | String | 刷新令牌 |
token_type | String | Bearer |
expires_in | Number | 访问令牌有效期,单位为秒 |
返回示例
{
"access_token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg1MjUyNjA2MzI0MzM1ODE5NkJDQkFFMkQ3a0hrUiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODUyNTI2MCwiZXhwIjoxNjM4NTI4ODYwLCJzdWIiOiIxMC4yNTQuM...
"refresh_token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg1MjUyNjA2MzI0MzM1ODE5NkJDQkFFMkQ3a0hrUiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODUyNTI2MCwiZXhwIjoxNjM4Nzg0NDYwLCJzdWIiOiIxMC4yNTQuM...
"token_type": "bearer",
"expires_in": 3600
}
3. 撤销令牌(授权码模式)
撤销访问令牌或刷新令牌
请求示例
POST /v5/users/oauth/revoke
body {
"token": "eyJhbGciOiJIUzUxMiIsImNpZCI6ImFhIn0.eyJqdGkiOiJleDExMDE2Mzg0MzU4NTk3MDc2MTFERjk0OEZFNUNFQTUwZUlURiIsInVpZCI6IlEybEZxMnY2N0VybnVMZ0o1cFYzdUE9PSIsIm1pZCI6InFGbG5lVEc4dnlJeDNMSnNSa29qZ0E9PSIsImlhdCI6MTYzODQzNTg1OSwiZXhwIjoxNjM4Njk1MDU5LCJzdWIiOiIxMC4yNTQuMjcuMTIwIiwiYW95IjoiOSIsInZlciI6IjEiLCJndHkiOiJyZWZyZXNoIn0.Kfs0jBfbfsrnc4HE44ShXNxp2TuHio5R8GpBebEx5rALA_BTgqBtJRwC6330TdU9J6kOZiNp97Zd8RNPJYjXHw"
}
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
token | String | 是 | 待撤销的令牌 |
返回参数
参数名 | 参数类型 | 描述 |
---|---|---|
code | String | 错误码 |
msg | String | 错误描述 |
返回示例
{
"code": "0",
"msg": ""
}
二、PKCE 模式令牌请求
1. 获取令牌(PKCE模式)
代码示例
OKEXOAuthSDK.requestToken({
grant_type: 'authorization_code',
client_id: 'YOUR_CLIENT_ID',
code_verifier: 'YOUR_CODE_VERIFIER',
code,
term_id: termId
}).then((res) => {
const token = res.data.access_token;
console.log(`获取 token 成功:${token}`);
}).catch((err) => {
console.error(err?.msg);
});
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
grant_type | String | 是 | authorization_code: 获取访问令牌 |
client_id | String | 是 | 第三方应用 client_id |
code_verifier | String | 是 | 初始请求中生成的 code verifier,对应于发起授权请求中的 code_challenge 参数,仅适用于 PKCE 模式 |
code | String | 是 | 发起授权请求成功后获得的授权码 |
term_id | String | 是 | 设备号,用于绑定令牌,可通过生成设备号接口获取 |
2. 刷新令牌(PKCE模式)
代码示例
OKEXOAuthSDK.requestToken({
grant_type: 'refresh_token',
client_id: 'YOUR_CLIENT_ID',
your_refresh_token,
term_id: termId
}).then((res) => {
const token = res.data.access_token;
console.log(`获取 token 成功:${token}`)
}).catche((err) => {
console.err(err?.msg)
});
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
grant_type | String | 是 | refresh_token: 刷新访问令牌 |
client_id | String | 是 | 第三方应用 client_id |
refresh_token | String | 是 | 刷新令牌 |
term_id | String | 是 | 设备号,用于校验和令牌绑定的设备号是否一致 |
3. 撤销令牌(PKCE模式)
代码示例
OKEXOAuthSDK.revokeToken({ token, term_id: termId })
.then((res) => {
console.log('取消 token 成功');
})
.catch((err) => {
console.error(err?.msg);
});
请求参数
参数名 | 参数类型 | 是否必须 | 描述 |
---|---|---|---|
term_id | String | 是 | 设备号,用于校验和令牌绑定的设备号是否一致 |
token | String | 是 | 待撤销的令牌 |
错误码
错误码 | HTTP 状态码 | error_description (中) | error_description (英) |
---|---|---|---|
53000 | 400 | 无效的 token | Invalid token |
53001 | 400 | 无效的授权,用户已取消授权 | Authorization canceled |
53002 | 400 | token 已过期 | Token expired |
53003 | 400 | token 已撤销 | Token revoked |
53004 | 400 | 用户已被冻结 | Account has been frozen |
53005 | 400 | 刷新令牌不正确 | Wrong refresh token |
53006 | 401 | 无效的设备 | Invalid device |
53009 | 400 | 授权失败 | Authorization failed |
53010 | 400 | 参数 {0} 错误 | Parameter {0} error |
53011 | 400 | 必填参数 {0} 不能为空 | Parameter {0} cannot be empty |
53012 | 400 | 授权码已过期 | Authorization code expired |
53013 | 400 | 接口权限不足 | You need higher permissions |
53014 | 401 | 无效的 IP | Invalid IP |
53015 | 400 | 参数 {参数名} 长度超过最大限制 {长度} | Parameter {name} length cannot exceed {max length} |
53016 | 400 | 无效的 redirect_uri | Invalid redirect_uri |
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 时光·李记
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果