Le flux d'autorisation Spotify PKCE renvoie "code_verifier était incorrect"

Aug 19 2020

J'ai suivi le guide d'authentification de l' API Spotify pour authentifier mon application à l'aide de PKCE.

À partir de maintenant, j'utilise un vérificateur de code factice avec un défi pré-calculé pour le débogage. Ces valeurs ont été calculées à l'aide de plusieurs outils en ligne ( SHA256 , SHA256 , base64url , base64url ) et correspondent aux valeurs renvoyées par les fonctions de hachage / codage que j'ai écrites en Swift. N'hésitez pas à utiliser les liens ci-dessus pour les vérifier.

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

J'utilise ASWebAuthenticationSession pour faire ma demande initiale à l'étape 2, comme ceci:

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

Dans handleLoginResponse, j'analyse la réponse à l'étape 3 et fais une demande réseau pour l'étape 4 à l'aide d'Alamofire:

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 crée une interface pour faire des requêtes cURL à partir de Swift, et l'appel cURLDescription()me permet de voir exactement ce que la commande cURL réelle finit par être:

$ 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"

C'est un peu difficile à lire, mais je suis sûr que la demande est faite correctement.

Cependant, à l'étape 4, je reçois toujours ce message d'erreur du serveur:

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

J'ai essayé beaucoup de choses au cours de plusieurs heures et je n'arrive toujours pas à comprendre. Tous les pointeurs seraient très appréciés. Merci!

Réponses

2 Sn0w0dDev Sep 17 2020 at 01:55

Votre problème est que les octets bruts du hachage SHA sont ce qui doit être base64. Je travaille également sur une application qui utilise Alamofire et Spotify PKCE et j'ai eu des problèmes avec le défi du code. Ce que j'ai fait, c'est d'utiliser du code de la documentation Auth0 qui a été écrit pour Swift 3 et je l'ai modifié pour fonctionner avec 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"))

J'espère que cela aide et bonne chance à vous!

GaryArcher Aug 19 2020 at 18:27

Lorsque j'ai utilisé ce vérificateur PKCE en ligne, j'ai eu un défi différent pour le vérificateur, donc je réessayerais peut-être avec les valeurs que cet outil vous donne.

SÉCURITÉ GLOBALE

J'utiliserais une bibliothèque de sécurité respectée telle qu'AppAuth, afin que vous n'ayez pas besoin de coder la sécurité vous-même. Les bibliothèques nous aident à éviter les erreurs potentielles et nous obtenons de nouvelles fonctionnalités de sécurité gratuitement à l'avenir.

INTÉGRATION APPAUTH

Si vous êtes intéressé par cette approche, ces étapes et ressources pourraient s'avérer utiles:

  • Exécution de l'exemple AppAuth
  • Ensuite, mettez à jour votre configuration pour vous assurer qu'elle fonctionne avec Spotify
  • L'exemple Advanced AppAuth couvre des domaines plus détaillés pour surmonter les problèmes OAuth courants sur les mobiles

CODE

Voici un de mes codes Swift qui intègre les bibliothèques, et aucune gestion PKCE de bas niveau n'est nécessaire.