Spotify PKCE in Dart/Flutter: "code_verifier was incorrect"

823

After some more research I found out that the important thing that is happening is that the "=" at the end of the challenge has to be removed (Shouldn't base64Url do that?). Anyways, that is the working code:

EDITED CODE:

String getAuthUrl() {
    code = getRandomString(128);
    var hash = sha256.convert(ascii.encode(code));
    String code_challenge = base64Url.encode(hash.bytes).replaceAll("=", "").replaceAll("+", "-").replaceAll("/", "_");
    return Uri.parse(
            "https://accounts.spotify.com/authorize?response_type=code&client_id=${widget.client_id}&redirect_uri=http%3A%2F%2Flocalhost%2Fauth&scope=user-top-read&code_challenge=$code_challenge&code_challenge_method=S256")
        .toString();
  }


EDIT:
Further "+" has to be replaced with "-" and "/" with "_"!

Share:
823
Max
Author by

Max

Updated on December 24, 2022

Comments

  • Max
    Max over 1 year

    Using the Authorization Code Flow with PKCE of the Spotify-API I am getting the error that my code_verifier is incorrect, which has to be an encoding problem from what I know by now.
    {"error":"invalid_grant","error_description":"code_verifier was incorrect"}
    This is the original code I wrote:

    String getAuthUrl() {
        code = getRandomString(128);
        // saveToPrefs("verfifier_code", code);
        var hash = sha256.convert(ascii.encode(code));
        String code_challenge = base64Url.encode(hash.bytes);
        return Uri.parse(
                "https://accounts.spotify.com/authorize?response_type=code&client_id=${widget.client_id}&redirect_uri=http%3A%2F%2Flocalhost%2Fauth&scope=user-top-read&code_challenge=$code_challenge&code_challenge_method=S256")
            .toString();
      }
    
    

    This is how I understand the Spotify-Authorisation-Guide (https://developer.spotify.com/documentation/general/guides/authorization-guide/).

    After finding this post (https://stackoverflow.com/a/63174909/14266484) I tried porting the fix to Dart but failed. As far as I understand it the code hashes the in ascii encoded code_verifier and then uses btoa() to convert it into ascii again. This function also seems to do base64 (but not base64Url, maybe this is why certain parts have to be replaced manually?).

    String getAuthUrl() {
        // also tried static verifier_codes for debugging, so the getRandomString() function is working properly
        code = getRandomString(128);
        // saveToPrefs("verfifier_code", code);
        var hash = sha256.convert(ascii.encode(code));
        // this does not work with either base64 or base64Url
        String code_challenge = base64.encode(hash.bytes).replaceAll(RegExp(r"/\+/g"), '-').replaceAll(RegExp(r"/\//g"), '_').replaceAll(RegExp(r"/=+$/"), '');
        return Uri.parse(
                "https://accounts.spotify.com/authorize?response_type=code&client_id=${widget.client_id}&redirect_uri=http%3A%2F%2Flocalhost%2Fauth&scope=user-top-read&code_challenge=$code_challenge&code_challenge_method=S256")
            .toString();
      }
    
    

    I also tried different ways of encoding:
    -Using String.codeUnits (But this is using UTF-16)
    -Getting a String for the sha256-function aswell the base64(-Url)-function with String.fromCharCodes() (which should be using ASCII?)
    -Switching between ASCII and UTF-8 (Which should not make a difference in that case as my verifier_code is made up of ASCII-Characters only)

    EDIT:
    To make the requests I use:

    var res = await http.post(endpoint, body: {"client_id":widget.client_id, "grant_type":"authorization_code", "code":value, "redirect_uri":"http://localhost/auth", "code_verifier":code});
    
  • cavargov
    cavargov about 3 years
    I spent a lot of time figuring this out, didn't succeed tho. This solved the problem immediately. Many thanks.
  • stefanS
    stefanS over 2 years
    Indeed, base64Url should do exactly what you are doing manually here. I am not sure what's the purpose of the base64Url codec in the convert package if it basically does the same as the base64 codec... Anyway, thanks a lot for your response, it saved me a lot of headache!