我们真的需要 client_secret 才能在 PKCE 流程上获得 access_token 吗?
Do we really need client_secret to get access_token on PKCE flow?
我正在构建 2 个应用;一个前端,一个后端。
后端将使用 Rails API + Doorkeeper Gem(oauth2 提供程序)构建,而前端将使用 React Native 构建。
目前,我正在使用“Client Credentials Grant Flow”,目前效果很好。但是经过一段时间的研究,这个流程不应该在客户端应用程序中使用,因为如果有人反编译应用程序,它会暴露 client_secret
。
我还阅读了“隐式授权流程”,它只需要 client_id
。但是现在这个流程好像有点老了?
并且根据这个:https://auth0.com/docs/api-auth/which-oauth-flow-to-use#is-the-client-a-single-page-app-
建议在“隐式授权流程”上使用“授权代码授权与 PKCE”。我能够让它工作,但问题是它仍然需要 client_secret
才能获得 access_token
,这是应该的吗?
这是我正在做的示例流程:
curl -X POST 'http://localhost:3000/oauth/authorize?client_id=xxxx&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=public&code_challenge=testchallenge&code_challenge_method=plain'
这将给我以下响应:
{
"status": "redirect",
"redirect_uri": {
"action": "show",
"code": "8quZ-EAiKKG2EKnQiSYs3xeFRCgsIwcTbaWNdjnpyFg"
}
}
然后我将使用上面的代码通过以下请求获取访问令牌:
curl -i http://localhost:3000/oauth/token \
-F grant_type="authorization_code" \
-F client_id="xxxx" \
-F client_secret="xxxx" \
-F code="8quZ-EAiKKG2EKnQiSYs3xeFRCgsIwcTbaWNdjnpyFg" \
-F redirect_uri="urn:ietf:wg:oauth:2.0:oob" \
-F code_verifier="testchallenge"
现在问题来了,为了将 code
换成 access_token
,我仍然需要 client_secret
。如果两者都只会公开我的 client_secret
,它与“客户端凭据授予流程”有何不同?
以上命令将return以下内容:
{
"access_token": "nQoorBqLxQH4qFpmlx3mGG6Cd_TfX4d3L3gAGOTwrFs",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "public",
"created_at": 1595517643
}
如果我不在请求中包含 client_secret
,这里是响应:
{
"error": "invalid_client",
"error_description": "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method."
}
所以这是我的问题:
- 我们真的需要
client_secret
才能在 PKCE 流程上获得 access_token
吗?
- 如果它只会公开
client_secret
,为什么建议使用“PKCE Flow”?
- 它与同样公开
client_secret
的“客户端凭据授予流程”有何不同?
Doorkeeper PKCE 文档:https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-PKCE-flow
- 我们真的需要 client_secret 才能在 PKCE 流程上获得 access_token 吗?
视情况而定。最初引入 PKCE 是为了保护 public 客户端(无法保护机密的客户端)。但是最近,随着实践,PKCE成为授权码授予的推荐(source)
2.1.1. Authorization Code Grant
Clients MUST prevent injection (replay) of authorization codes into
the authorization response by attackers. The use of PKCE [RFC7636]
is RECOMMENDED to this end. The OpenID Connect "nonce" parameter and
ID Token Claim [OpenID] MAY be used as well. The PKCE challenge or
OpenID Connect "nonce" MUST be transaction-specific and securely
bound to the client and the user agent in which the transaction was
started.
Note: although PKCE so far was designed as a mechanism to protect
native apps, this advice applies to all kinds of OAuth clients,
including web applications.
- 如果它只会公开 client_secret,为什么建议使用“PKCE Flow”?
简而言之,避免授权码重放攻击(spec - introduction)。这发生在最终用户的设备内部,而不是数据传输中。 OAuth 2.0 令牌请求必须使用 TLS。
- 它与同样公开 client_secret 的“客户端凭据授予流程”有何不同?
由于令牌请求是通过 TLS 完成的,因此不会公开凭据。
我认为在您的情况下,客户是保密客户 (spec - client types)。所以我建议在授权服务器中检查这方面。
我正在 Rails 控制台中创建这样的 Doorkeeper::Application
:
Doorkeeper::Application.create :name => 'Test App', :uid => 'xxxx', :secret => 'xxxx', :redirect_uri => 'urn:ietf:wg:oauth:2.0:oob'
看来我需要将 Doorkeeper::Application
的 机密 字段设置为 false
才能在没有 [的情况下获得 access_token
=16=].
所以上面的代码会变成:
Doorkeeper::Application.create :name => 'Test App', :uid => 'xxxx', :secret => 'xxxx', :redirect_uri => 'urn:ietf:wg:oauth:2.0:oob', :confidential => false
我在以下地方找到了解决方案:
https://github.com/doorkeeper-gem/doorkeeper/blob/master/spec/requests/flows/authorization_code_spec.rb#L348
更新
与此同时,建议发生了变化(另请参阅 Kavindu 的回答以获取参考),我想确保此回答中也强调了这一点。将 PKCE 作为附加的安全层 包括在客户端机密之外,这对于 机密客户端 也很有意义。客户端密码允许授权服务器(身份提供者)确定客户端的身份。
虽然 PKCE 允许确保尝试为令牌交换授权代码的一方是 首先实际请求授权代码的一方。因此,为了避免拦截情况,即使对于机密客户端,它也会在顶部添加 PKCE。
但请记住,并非所有身份提供者都支持同时使用 PKCE 和客户端密码(目前)。
至于 Doorkeeper 所述的问题,此行为可能同时也发生了变化。
原答案
使用 PKCE 的授权代码流是为 客户端无法安全保护机密 的设置发明的。因此,当使用带有 PKCE 的授权代码流时,您不需要机密,或者更准确地说,客户端机密没有任何意义。
您遇到的问题似乎是 Doorkeeper 错误(参见 https://github.com/doorkeeper-gem/doorkeeper/issues/1089)。所以我担心在他们修复它之前你将不得不使用一些虚拟客户端秘密。
但是只要 Doorkeeper 正确地实现了 PKCE 流程的其余部分,这应该不是问题,因为此流程不依赖于任何静态客户端机密,而是使用您已经指出的动态创建的代码验证程序。
我不确定我是否正确理解了您使用的处理登录的客户端类型。如果它是 SPA 或移动应用程序,您应该使用 PKCE 的授权代码流,但如果您正在实现一个“经典”Web 应用程序,其中登录是在某些后端服务中处理的,您应该使用客户端密码作为正常的授权代码流可以信任后端来保护秘密。
顺便说一句,我刚刚检查了我正在开发的一个项目中的代码,我在其中构建了一些 Angular SPA,它通过 Auth0 作为身份提供者进行身份验证。我在那里使用 PKCE 的授权代码流,我绝对不会向 Auth0 发送任何客户端机密,因为显然该流是按预期实现的。所以这确实是 Doorkeeper 的问题。
另一件事:我不知道您提供的请求是否只是示例,但我不会使用方法 plain 将代码验证程序转换为代码挑战而是使用 S256 等安全方法,而不是按照您在问题中引用的 RFC 中的建议。
我正在构建 2 个应用;一个前端,一个后端。
后端将使用 Rails API + Doorkeeper Gem(oauth2 提供程序)构建,而前端将使用 React Native 构建。
目前,我正在使用“Client Credentials Grant Flow”,目前效果很好。但是经过一段时间的研究,这个流程不应该在客户端应用程序中使用,因为如果有人反编译应用程序,它会暴露 client_secret
。
我还阅读了“隐式授权流程”,它只需要 client_id
。但是现在这个流程好像有点老了?
并且根据这个:https://auth0.com/docs/api-auth/which-oauth-flow-to-use#is-the-client-a-single-page-app-
建议在“隐式授权流程”上使用“授权代码授权与 PKCE”。我能够让它工作,但问题是它仍然需要 client_secret
才能获得 access_token
,这是应该的吗?
这是我正在做的示例流程:
curl -X POST 'http://localhost:3000/oauth/authorize?client_id=xxxx&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=public&code_challenge=testchallenge&code_challenge_method=plain'
这将给我以下响应:
{
"status": "redirect",
"redirect_uri": {
"action": "show",
"code": "8quZ-EAiKKG2EKnQiSYs3xeFRCgsIwcTbaWNdjnpyFg"
}
}
然后我将使用上面的代码通过以下请求获取访问令牌:
curl -i http://localhost:3000/oauth/token \
-F grant_type="authorization_code" \
-F client_id="xxxx" \
-F client_secret="xxxx" \
-F code="8quZ-EAiKKG2EKnQiSYs3xeFRCgsIwcTbaWNdjnpyFg" \
-F redirect_uri="urn:ietf:wg:oauth:2.0:oob" \
-F code_verifier="testchallenge"
现在问题来了,为了将 code
换成 access_token
,我仍然需要 client_secret
。如果两者都只会公开我的 client_secret
,它与“客户端凭据授予流程”有何不同?
以上命令将return以下内容:
{
"access_token": "nQoorBqLxQH4qFpmlx3mGG6Cd_TfX4d3L3gAGOTwrFs",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "public",
"created_at": 1595517643
}
如果我不在请求中包含 client_secret
,这里是响应:
{
"error": "invalid_client",
"error_description": "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method."
}
所以这是我的问题:
- 我们真的需要
client_secret
才能在 PKCE 流程上获得access_token
吗? - 如果它只会公开
client_secret
,为什么建议使用“PKCE Flow”? - 它与同样公开
client_secret
的“客户端凭据授予流程”有何不同?
Doorkeeper PKCE 文档:https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-PKCE-flow
- 我们真的需要 client_secret 才能在 PKCE 流程上获得 access_token 吗?
视情况而定。最初引入 PKCE 是为了保护 public 客户端(无法保护机密的客户端)。但是最近,随着实践,PKCE成为授权码授予的推荐(source)
2.1.1. Authorization Code Grant
Clients MUST prevent injection (replay) of authorization codes into
the authorization response by attackers. The use of PKCE [RFC7636]
is RECOMMENDED to this end. The OpenID Connect "nonce" parameter and ID Token Claim [OpenID] MAY be used as well. The PKCE challenge or
OpenID Connect "nonce" MUST be transaction-specific and securely
bound to the client and the user agent in which the transaction was
started.Note: although PKCE so far was designed as a mechanism to protect
native apps, this advice applies to all kinds of OAuth clients,
including web applications.
- 如果它只会公开 client_secret,为什么建议使用“PKCE Flow”?
简而言之,避免授权码重放攻击(spec - introduction)。这发生在最终用户的设备内部,而不是数据传输中。 OAuth 2.0 令牌请求必须使用 TLS。
- 它与同样公开 client_secret 的“客户端凭据授予流程”有何不同?
由于令牌请求是通过 TLS 完成的,因此不会公开凭据。
我认为在您的情况下,客户是保密客户 (spec - client types)。所以我建议在授权服务器中检查这方面。
我正在 Rails 控制台中创建这样的 Doorkeeper::Application
:
Doorkeeper::Application.create :name => 'Test App', :uid => 'xxxx', :secret => 'xxxx', :redirect_uri => 'urn:ietf:wg:oauth:2.0:oob'
看来我需要将 Doorkeeper::Application
的 机密 字段设置为 false
才能在没有 [的情况下获得 access_token
=16=].
所以上面的代码会变成:
Doorkeeper::Application.create :name => 'Test App', :uid => 'xxxx', :secret => 'xxxx', :redirect_uri => 'urn:ietf:wg:oauth:2.0:oob', :confidential => false
我在以下地方找到了解决方案: https://github.com/doorkeeper-gem/doorkeeper/blob/master/spec/requests/flows/authorization_code_spec.rb#L348
更新
与此同时,建议发生了变化(另请参阅 Kavindu 的回答以获取参考),我想确保此回答中也强调了这一点。将 PKCE 作为附加的安全层 包括在客户端机密之外,这对于 机密客户端 也很有意义。客户端密码允许授权服务器(身份提供者)确定客户端的身份。
虽然 PKCE 允许确保尝试为令牌交换授权代码的一方是 首先实际请求授权代码的一方。因此,为了避免拦截情况,即使对于机密客户端,它也会在顶部添加 PKCE。
但请记住,并非所有身份提供者都支持同时使用 PKCE 和客户端密码(目前)。
至于 Doorkeeper 所述的问题,此行为可能同时也发生了变化。
原答案
使用 PKCE 的授权代码流是为 客户端无法安全保护机密 的设置发明的。因此,当使用带有 PKCE 的授权代码流时,您不需要机密,或者更准确地说,客户端机密没有任何意义。
您遇到的问题似乎是 Doorkeeper 错误(参见 https://github.com/doorkeeper-gem/doorkeeper/issues/1089)。所以我担心在他们修复它之前你将不得不使用一些虚拟客户端秘密。
但是只要 Doorkeeper 正确地实现了 PKCE 流程的其余部分,这应该不是问题,因为此流程不依赖于任何静态客户端机密,而是使用您已经指出的动态创建的代码验证程序。
我不确定我是否正确理解了您使用的处理登录的客户端类型。如果它是 SPA 或移动应用程序,您应该使用 PKCE 的授权代码流,但如果您正在实现一个“经典”Web 应用程序,其中登录是在某些后端服务中处理的,您应该使用客户端密码作为正常的授权代码流可以信任后端来保护秘密。
顺便说一句,我刚刚检查了我正在开发的一个项目中的代码,我在其中构建了一些 Angular SPA,它通过 Auth0 作为身份提供者进行身份验证。我在那里使用 PKCE 的授权代码流,我绝对不会向 Auth0 发送任何客户端机密,因为显然该流是按预期实现的。所以这确实是 Doorkeeper 的问题。
另一件事:我不知道您提供的请求是否只是示例,但我不会使用方法 plain 将代码验证程序转换为代码挑战而是使用 S256 等安全方法,而不是按照您在问题中引用的 RFC 中的建议。