Spotify PKCE 授权流程返回“code_verifier 不正确”

Posted

技术标签:

【中文标题】Spotify PKCE 授权流程返回“code_verifier 不正确”【英文标题】:Spotify PKCE authorization flow returns "code_verifier was incorrect" 【发布时间】:2020-12-08 11:05:40 【问题描述】:

我一直关注Spotify API's Authentication Guide 使用 PKCE 对我的应用进行身份验证。

到目前为止,我正在使用带有预先计算的 挑战 的虚拟 代码验证器 进行调试。这些值是使用多个在线工具(SHA256、SHA256、base64url、base64url)计算得出的,并且与我用 Swift 编写的散列/编码函数返回的值相匹配。随意使用上面的链接来验证这些。

let verifier = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
let challenge = "66d34fba71f8f450f7e45598853e53bfc23bbd129027cbb131a2f4ffd7878cd0"
let challengeBase64URL = "NjZkMzRmYmE3MWY4ZjQ1MGY3ZTQ1NTk4ODUzZTUzYmZjMjNiYmQxMjkwMjdjYmIxMzFhMmY0ZmZkNzg3OGNkMA"

我在第 2 步中使用 ASWebAuthenticationSession 发出初始请求,如下所示:

var components = URLComponents()
components.scheme = "https"
components.host = "accounts.spotify.com"
components.path = "/authorize"
components.queryItems = [
    URLQueryItem(name: "client_id", value: SpotifyClientID),
    URLQueryItem(name: "response_type", value: "code"),
    URLQueryItem(name: "redirect_uri", value: SpotifyRedirectURL.absoluteString),
    URLQueryItem(name: "code_challenge_method", value: "S256"),
    URLQueryItem(name: "code_challenge", value: challenge),
    URLQueryItem(name: "state", value: "testing-state"),
    URLQueryItem(name: "scope", value: "user-follow-read")
]
let urlString = components.url!.absoluteString

guard let authURL = URL(string: urlString) else  return 
print(authURL)
let authSession = ASWebAuthenticationSession(url: authURL, callbackURLScheme: callbackScheme, completionHandler: handleLoginResponse)
authSession.presentationContextProvider = self
authSession.prefersEphemeralWebBrowserSession = true
authSession.start()

handleLoginResponse 中,我解析步骤 3 中的响应并使用 Alamofire 为步骤 4 发出网络请求:

guard let items = URLComponents(string: callbackURL?.absoluteString ?? "").queryItems else  return 
let authCode = items[0].value!
let endpoint = "https://accounts.spotify.com/api/token"

let headers = HTTPHeaders(["Content-Type": "application/x-www-form-urlencoded"])
let parameters: [String: String] = [
    "client_id": SpotifyClientID,
    "grant_type": "authorization_code",
    "code": authCode,
    "redirect_uri": SpotifyRedirectURL.absoluteString,
    "code_verifier": verifier!
]
AF.request(endpoint,
           method: .post,
           parameters: parameters,
           encoder: URLEncodedFormParameterEncoder.default,
           headers: headers
).cURLDescription()  description in
    print(description)

.responseJSON()  (json) in
    print(json)

Alamofire 创建了一个接口来从 Swift 中发出 cURL 请求,调用 cURLDescription() 可以让我准确地看到实际的 cURL 命令最终是什么:

$ curl -v \
    -X POST \
    -b "__Host-device_id=AQBHyRKdulrPJU6vY5xlua1xKOZBtBZVcrW9IK-X0LQ_MPj5x3N4mZkF4OzgLMdQwviWUxJ2dY6d49d0QpjG0ayFtCfrhwzG5-g" \
    -H "User-Agent: SpotifyUserGraph/1.0 (hl999.SpotifyUserGraph; build:1; ios 14.0.0) Alamofire/5.1.0" \
    -H "Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8" \
    -H "Accept-Language: en-US;q=1.0, zh-Hans-US;q=0.9, ko-US;q=0.8" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "client_id=e11fb810282946569aab8f89e52f78d5&code=AQC3Lm3KDPFCg3mBjSAiXMyvjdn5GvUJCjjCTQzPhAFe5mLntAHcAeiEufXcCv3Jne2qn345MZxBNiCggO-35mn6AAFsjRlm5lPynyC6clWABSzBK1OdWIynTlf0CiyR8vWYeO54GHHEXBSzj6URKWnAiXuxTUV6n1Axra6Oet8FY6-0jwU0CNGMaB91q1JFXlyl5J9JvrRtrP3s2Ef8Xb5A7gcCzqW6RHRzO0--BKiPHFnprK0SitiLxi-md2aaMnS2aHsRTqvc_NfFcuRpFR05WmSm6Gvkk_9trSBqRvVZYuGs-Ap3-ydVGk7BCqNc3lpbh4Jku6W_930fOg9kI__zRA&code_verifier=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&grant_type=authorization_code&redirect_uri=hl999-spotifyusergraph%3A//spotify-login-callback" \
    "https://accounts.spotify.com/api/token"

阅读起来有点困难,但我很确定请求是正确的。

但是,在第 4 步,我总是从服务器收到此错误消息:

error = "invalid_grant";
"error_description" = "code_verifier was incorrect";

我在几个小时的过程中尝试了很多事情,但仍然无法弄清楚。任何指针将不胜感激。谢谢!

【问题讨论】:

【参考方案1】:

您的问题是 SHA 哈希的原始字节需要进行 base64 处理。我还在开发一个使用 Alamofire 和 Spotify PKCE 的应用程序,但遇到了代码挑战。我所做的是使用为 Swift 3 编写的 Auth0 documentation 中的一些代码,并将其修改为与 Swift 5 一起使用:

import Foundation
import CommonCrypto
 
func challenge(verifier: String) -> String 
    
    guard let verifierData = verifier.data(using: String.Encoding.utf8) else  return "error" 
        var buffer = [UInt8](repeating: 0, count:Int(CC_SHA256_DIGEST_LENGTH))
 
        verifierData.withUnsafeBytes 
            CC_SHA256($0.baseAddress, CC_LONG(verifierData.count), &buffer)
        
    let hash = Data(_: buffer)
    print(hash)
    let challenge = hash.base64EncodedData()
    return String(decoding: challenge, as: UTF8.self)
        .replacingOccurrences(of: "+", with: "-")
        .replacingOccurrences(of: "/", with: "_")
        .replacingOccurrences(of: "=", with: "")
        .trimmingCharacters(in: .whitespaces)

print(challenge(verifier: "ExampleVerifier"))

希望这对您有所帮助并祝您好运!

【讨论】:

我几乎放弃了所有希望,然后我找到了这个【参考方案2】:

与this other one 略有不同的版本,根据平台使用CryptoKitSwiftCrypto

func base64URLEncode<S>(octets: S) -> String where S : Sequence, UInt8 == S.Element 
    let data = Data(octets)
    return data
        .base64EncodedString()
        .replacingOccurrences(of: "=", with: "")
        .replacingOccurrences(of: "+", with: "-")
        .replacingOccurrences(of: "/", with: "_")
        .trimmingCharacters(in: .whitespaces)


func challenge(for verifier: String) -> String 
    let challenge = verifier
        .data(using: .ascii)
        .map  SHA256.hash(data: $0) 
        .map  base64URLEncode(octets: $0) 

    if let challenge = challenge 
        return challenge
     else 
        fatalError()
    

整个事情在this blog post上都有详细说明。

【讨论】:

【参考方案3】:

当我使用this online PKCE verifier 时,验证器遇到了不同的挑战,因此我可能会使用工具提供的值重试。

整体安全

我会使用受人尊敬的安全库,例如 AppAuth,这样您就不需要自己编写安全代码。库可以帮助我们避免潜在的错误,并且我们将来会免费获得新的安全功能。

APPAUTH 集成

如果对这种方法感兴趣,我的这些步骤和资源可能会很有用:

Running the AppAuth Sample 然后更新您的配置以确保它适用于 Spotify Advanced AppAuth Sample 涵盖了一些更详细的领域,以解决常见的移动 OAuth 问题

代码

这里有一些 Swift code of mine 集成了库,不需要低级别的 PKCE 处理。

【讨论】:

以上是关于Spotify PKCE 授权流程返回“code_verifier 不正确”的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Kotlin 中使用 PKCE 实现 Spotify 授权代码

Spotify PKCE。错误无效的客户端密码

在 ReactJS 中的 Spotify API 上为 PKCE 身份验证创建代码验证器和质询

在 PKCE 增强的授权代码流中保护 code_verifier 的最佳实践

从 iOS 授权流程中完全阻止 Spotify 注册

Spotify API 授权授予流程状态 415