IdentityServer4 PKCE错误:"转换后的代码验证器与代码挑战不匹配"

11

我无法使用Postman实现IdentityServer4 PKCE授权。

使用在线工具创建必要的部分:

选择一个随机字符串:

1234567890

获取它的SHA-256哈希值:

c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646

对哈希进行Base64编码,以获取代码挑战:

Yzc3NWU3Yjc1N2VkZTYzMGNkMGFhMTExM2JkMTAyNjYxYWIzODgyOWNhNTJhNjQyMmFiNzgyODYyZjI2ODY0Ng==

在浏览器中导航到以下网址,填写我的凭据并从片段重定向网址中检索代码。

GET https://localhost:5000/connect/authorize
?client_id=pkceclient
&scope=openid
&response_type=code
&redirect_uri=https://jwt.ms
&state=abc
&nonce=xyz  
&code_challenge=Yzc3NWU3Yjc1N2VkZTYzMGNkMGFhMTExM2JkMTAyNjYxYWIzODgyOWNhNTJhNjQyMmFiNzgyODYyZjI2ODY0Ng==
&code_challenge_method=S256

当我兑换令牌的代码时,我传递了code_verifier(SHA-256哈希),但我的IdentityServer记录了以下错误:

"Transformed code verifier does not match code challenge".

POST https://localhost:5000/connect/token
client_id=pkceclient
grant_type=authorization_code
code:-CesrmjPYjdLdDd5AviOZpR6GdjjkZia_ZapoJdGUZI
redirect_uri=https://jwt.ms
code_verifier=c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646
在他的博客文章中,作者使用以下代码生成部分内容。
var verifier = CryptoRandom.CreateRandomKeyString(64);
var challenge = verifier.ToCodeChallenge();

但我在代码库中找不到ToCodeChallenge方法的代码。

为什么我手动生成的挑战与验证过程中使用的不匹配,我错过了什么?

4个回答

22

在编写这个问题时,我遇到了PKCE的规范文档,并发现以下内容:

code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

事实证明,在我使用的在线工具中没有执行ASCII部分。

按照代码中的步骤进行实现,当我将之前的值代入时,通过了该过程的第二步验证。

var codeVerifier = "c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646";
var codeVerifierBytes = Encoding.ASCII.GetBytes(codeVerifier);
var hashedBytes = codeVerifierBytes.Sha256();
var transformedCodeVerifier = Base64Url.Encode(hashedBytes);

code_challenge:51FaJvQFsiNdiFWIq2EMWUKeAqD47dqU_cHzJpfHl-Q

code_verifier:c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646


1
感谢您更新自己的问题并提供解决方案。这为我节省了大量时间。 - datoml
@jhhwilliams,你能详细说明一下你所说的“我使用的在线工具”吗?据我了解,code_challenge是由客户端创建的。你是不是在说服务端上的IdentityServer4没有正确转换code_verifier? - Ross Brasseaux
@Lopsided,在开发我们的身份验证功能时,我使用Postman来了解和测试IdentityServer4流程。为了生成code_challenge,我使用了一个“在线工具”来哈希和base64编码我的1234567890字符串,这就是ASCII转换缺失的地方。 - jhhwilliams

2
这里有一个选定答案的小改进,无需使用Sha256()扩展方法(credit)。 code_verifier随机生成器(用于/connect/token端点):
private string GenerateCodeVerifier()
{
    var rng = RandomNumberGenerator.Create();

    var bytes = new byte[32];
    rng.GetBytes(bytes);

    // It is recommended to use a URL-safe string as code_verifier.
    // See section 4 of RFC 7636 for more details.
    var code_verifier = Convert.ToBase64String(bytes)
        .TrimEnd('=')
        .Replace('+', '-')
        .Replace('/', '_');

    return code_verifier;
}

基于code_verifier生成器的code_challenge(用于/connect/authorize端点):
private string GenerateCodeChallenge(string code_verifier)
{
    var code_challenge = string.Empty;
    using (var sha256 = SHA256.Create())
    {
        var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(code_verifier));
        code_challenge = Convert.ToBase64String(challengeBytes)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace('/', '_');

        return code_challenge;
    }
}

使用方法:

using System;
using System.Security.Cryptography;
using System.Text;

[TestMethod]
public void CodesTest()
{
    string code_verifier = GenerateCodeVerifier();
    Console.WriteLine("code_verifier:");
    Console.WriteLine(code_verifier);

    string code_challenge = GenerateCodeChallenge(code_verifier);
    Console.WriteLine("code_challenge:");
    Console.WriteLine(code_challenge);
}

将输出:
code_verifier:
3t1_Ve6NezEoLtj-7GKAWuXOOEUXe0z9Bd-uKoZeBnE
code_challenge:
cmcJe_eAcSGnEema7PXUEDZZOSofeaUDhKJC5P--uOY

在我的文章中有附加信息


1

这让我困惑了一段时间,因为我以为RFC提到了base64编码。很容易忽略,但实际上它说的是BASE64URL-ENCODE而不仅仅是BASE64 ;)

BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == code_challenge

PKCE rfc文档甚至包含一个C#代码示例来进行编码:

https://www.rfc-editor.org/rfc/rfc7636#appendix-A

static string base64urlencode(byte [] arg)
 {
   string s = Convert.ToBase64String(arg); // Regular base64 encoder
   s = s.Split('=')[0]; // Remove any trailing '='s
   s = s.Replace('+', '-'); // 62nd char of encoding
   s = s.Replace('/', '_'); // 63rd char of encoding
   return s;
 }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接