Spotify PKCE 인증 흐름이 "code_verifier was invalid"를 반환합니다.

Aug 19 2020

PKCE를 사용하여 내 앱을 인증 하기 위해 Spotify API의 인증 가이드 를 따르고 있습니다.

현재 저는 디버깅을 위해 미리 계산 된 도전 과 함께 더미 코드 검증기 를 사용하고 있습니다. 이 값은 여러 온라인 도구 ( SHA256 , SHA256 , base64url , base64url )를 사용하여 계산 되었으며 Swift에서 작성한 해싱 / 인코딩 함수에서 반환 된 값과 일치합니다. 위의 링크를 사용하여 확인하십시오.

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

ASWebAuthenticationSession을 사용하여 2 단계에서 다음과 같이 초기 요청을합니다.

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()

에서는 handleLoginResponse3 단계의 응답을 구문 분석하고 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";

몇 시간 동안 많은 것을 시도했지만 여전히 알아낼 수 없습니다. 어떤 포인터라도 대단히 감사하겠습니다. 감사!

답변

2 Sn0w0dDev Sep 17 2020 at 01:55

문제는 SHA 해시의 원시 바이트가 base64로 지정되어야한다는 것입니다. 또한 Alamofire 및 Spotify PKCE를 사용하는 앱을 개발 중이며 코드 문제에 문제가 있습니다. 내가 한 일은 Swift 3 용으로 작성된 Auth0 문서의 일부 코드를 사용 하여 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"))

이것이 당신에게 도움과 행운을 빕니다!

GaryArcher Aug 19 2020 at 18:27

이 온라인 PKCE 검증기를 사용했을 때 검증 자에 대해 다른 도전을 받았으므로 도구가 제공하는 값으로 다시 시도 할 것입니다.

전반적인 보안

AppAuth와 같은 존경받는 보안 라이브러리를 사용하므로 보안을 직접 코딩 할 필요가 없습니다. 라이브러리는 잠재적 인 실수를 방지하는 데 도움이되며 향후 새로운 보안 기능을 무료로 제공합니다.

APPAUTH 통합

이 접근 방식에 관심이 있다면 다음 단계와 리소스가 유용 할 수 있습니다.

  • AppAuth 샘플 실행
  • 그런 다음 구성을 업데이트하여 Spotify와 함께 작동하는지 확인하십시오.
  • 고급 AppAuth 샘플 은 일반적인 모바일 OAuth 문제를 극복하기 위해 좀 더 자세한 영역을 다룹니다.

암호

다음은 라이브러리를 통합하는 내 Swift 코드이며 저수준 PKCE 처리가 필요하지 않습니다.