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

问题描述

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

到目前为止,我正在使用带有预先计算的挑战的虚拟代码验证器进行调试。这些值是使用多个在线工具(SHA256SHA256base64urlbase64url)计算得出的,并且与我在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";

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

解决方法

您的问题是SHA哈希的原始字节是需要base64的。我还在开发一个使用Alamofire和Spotify PKCE的应用程序,但遇到了代码挑战。我所做的是使用Auth0 documentation中为Swift 3编写的一些代码,并对其进行了修改以与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"))

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

,

当我使用this online PKCE verifier时,对验证者提出了不同的挑战,因此我可能会尝试使用该工具为您提供的值。

总体安全性

我将使用受人尊敬的安全性库,例如AppAuth,这样您就无需自己编写安全性代码。图书馆可帮助我们避免潜在的错误,并且将来我们会免费提供新的安全功能。

APPAUTH集成

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

代码

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

,

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: "_")
        .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 上有详细说明。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...