El flujo de autorización de Spotify PKCE devuelve "code_verifier era incorrecto"
He estado siguiendo la Guía de autenticación de la API de Spotify para autenticar mi aplicación usando PKCE.
A partir de ahora, estoy usando un verificador de código ficticio con un desafío precalculado para la depuración. Estos valores se calcularon utilizando varias herramientas en línea ( SHA256 , SHA256 , base64url , base64url ) y coinciden con los valores devueltos por las funciones hash / codificación que he escrito en Swift. Siéntase libre de usar los enlaces anteriores para verificarlos.
let verifier = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
let challenge = "66d34fba71f8f450f7e45598853e53bfc23bbd129027cbb131a2f4ffd7878cd0"
let challengeBase64URL = "NjZkMzRmYmE3MWY4ZjQ1MGY3ZTQ1NTk4ODUzZTUzYmZjMjNiYmQxMjkwMjdjYmIxMzFhMmY0ZmZkNzg3OGNkMA"
Utilizo ASWebAuthenticationSession para realizar mi solicitud inicial en el paso 2, así:
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()
En handleLoginResponse
, analizo la respuesta en el paso 3 y hago una solicitud de red para el paso 4 usando 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 crea una interfaz para realizar solicitudes de cURL desde Swift, y llamar cURLDescription()
me permite ver exactamente cuál es el comando cURL real:
$ 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"
Es un poco difícil de leer, pero estoy bastante seguro de que la solicitud se realizó correctamente.
Sin embargo, en el paso 4, siempre recibo este mensaje de error del servidor:
error = "invalid_grant";
"error_description" = "code_verifier was incorrect";
He intentado muchas cosas en el transcurso de varias horas y todavía no puedo resolverlo. Cualquier sugerencia será muy apreciada. ¡Gracias!
Respuestas
Su problema es que los bytes sin procesar del hash SHA son los que deben basarse en 64. También estoy trabajando en una aplicación que usa Alamofire y Spotify PKCE y tuve problemas con el desafío del código. Lo que hice fue usar un código de la documentación de Auth0 que se escribió para Swift 3 y lo modifiqué para que funcione con 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"))
¡Espero que esto te ayude y buena suerte!
Cuando utilicé este verificador PKCE en línea , obtuve un desafío diferente para el verificador, así que tal vez volvería a intentar con los valores que le brinda la herramienta.
SEGURIDAD GENERAL
Usaría una biblioteca de seguridad respetada como AppAuth, para que no necesite codificar la seguridad usted mismo. Las bibliotecas nos ayudan a evitar posibles errores y obtenemos nuevas funciones de seguridad de forma gratuita en el futuro.
INTEGRACIÓN APPAUTH
Si está interesado en este enfoque, estos pasos y recursos míos podrían resultar útiles:
- Ejecución de la muestra de AppAuth
- Luego actualice su configuración para asegurarse de que funcione con Spotify
- La muestra de AppAuth avanzada cubre algunas áreas más detalladas para superar los problemas comunes de OAuth móvil
CÓDIGO
Aquí hay un código Swift mío que integra las bibliotecas, y no se necesita manejo de PKCE de bajo nivel.