IdentityServer4 PKCE error: "Transformed code verifier does not match code challenge"

IdentityServer4 PKCE error: "Transformed code verifier does not match code challenge"

我无法获得使用 Postman 工作的 IdentityServer4 PKCE 授权。

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

选择一个随机字符串:

1234567890

获取其 SHA-256 哈希值:

c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646

Base64 编码哈希以获得代码挑战:

Yzc3NWU3Yjc1N2VkZTYzMGNkMGFhMTExM2JkMTAyNjYxYWIzODgyOWNhNTJhNjQyMmFiNzgyODYyZjI2ODY0Ng==

在浏览器中,我导航到以下 URL,填写我的凭据并从零散的重定向中检索代码 URL。

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

在他的blog post中,作者使用以下代码生成零件。

var verifier = CryptoRandom.CreateRandomKeyString(64);
var challenge = verifier.ToCodeChallenge();

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

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

在整理这个问题时,我看到了 PKCE 的 specification 文档并找到了以下行:

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

以下链接有助于实现 PKCE-AuthZ-Code 流程。

https://auth0.com/docs/api-auth/tutorials/authorization-code-grant-pkce

https://github.com/gilbert-fernandes/S256Code/blob/master/src/S256Code.java

此处对所选答案略作改进,无需 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_challenge 生成器基于 code_verifier(对于 /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

我的 article

中的其他信息