Announcements

Help Wizard

Step 1

NEXT STEP

FAQs

Please see below the most popular frequently asked questions.

Loading article...

Loading faqs...

VIEW ALL

Ongoing Issues

Please see below the current ongoing issues which are under investigation.

Loading issue...

Loading ongoing issues...

VIEW ALL

Spotify PKCE Auth flow in Mobile Apps

Spotify PKCE Auth flow in Mobile Apps

My Question or Issue

 

Hi all, I am not usually one to post in forums as I enjoy getting to the bottom of my issues on my own, but I am having issues with the PKCE flow for the Spotify API. 

 

For context, I am developing an app in React Native using Expo. I'm not an expert in mobile development, and this is the first large project I am attempting in mobile development. 

The problem I am facing is that when my authentication flow executes, I am constantly getting an error 400 when attempting to exchange the code for a token. Let me walk you through what I have so far:

I have a very simple login screen like so:

return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
     <Button title="Login with Spotify" onPress={authenticateWithSpotify} />
</View>
);
 
authenticateWithSpotify calls this hook: 
const { authenticateWithSpotify, isAuthenticated } = useSpotifyAuth();
which calls my AuthenticationProvider context which wraps the entire App. This executes the authentication flow. The first step of my authentication flow is to generate a codeVerifier and codeChallenge. This is where I think the main issue may be. I call this function to create the verifier and challenge:

async function generatePKCEPair() {
try {
const codeVerifierLength = Math.floor(Math.random() * (128 - 43 + 1)) + 43;
const codeVerifier = generateCodeVerifier(codeVerifierLength);
const codeChallenge = await generateCodeChallenge(codeVerifier);

await AsyncStorage.setItem("spotify_code_verifier", codeVerifier);
await AsyncStorage.setItem("spotify_code_challenge", codeChallenge);
return codeChallenge;
}
catch (error) {
console.error("Error creating code verifier or code challenge: ", error);
return "";
}
}
 
The generateCodeVerifier() function will simply generate a random string of uppercase or lowercase letters and numbers, of random length between 43 and 128 characters. 
 
The generateCodeChallenge() function is my primary suspect for the error I am receiving. It looks like this:

export async function generateCodeChallenge(codeVerifier: string): Promise<string> {
try
{
const hash = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
codeVerifier,
{ encoding: Crypto.CryptoEncoding.BASE64 }
);

// Convert the hash to base64-url encoding
return hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
catch (e) {
console.error("Error in PKCE process: ", e);
return "Error in PKCE process!";
}
}

from my research, I know I need to hash the codeVerifier using SHA-256 and then encode it using base64-url encoding. On the Spotify documentation page for PKCE flow, they use this method: 

const sha256 = async (plain) => {const encoder = new TextEncoder()
const data = encoder.encode(plain)
return window.crypto.subtle.digest('SHA-256', data)
}
 
but whenever I tried using window.crypto.subtle.digest, I got an error that window was not available because the mobile app is not browser based (I am testing in an iOS emulator). 
 
After generating the PKCE values, I attempt to retrieve an auth code like this:

const authRequest = new AuthSession.AuthRequest({
responseType: AuthSession.ResponseType.Code,
clientId: SPOTIFY_CLIENT_ID,
scopes: ["user-read-private", "user-read-email"],
codeChallengeMethod: AuthSession.CodeChallengeMethod.S256,
codeChallenge: codeChallenge,
redirectUri: SPOTIFY_REDIRECT_URI1,
usePKCE: true,
});
 
const authResult = await authRequest.promptAsync(discovery);
 
if (authResult.type === "success") {
console.log("Auth code: " + authResult.params.code);
console.log("Auth state: " + authResult.params.state);
exchangeCodeForToken(authResult.params.code);
}
 
if a code is successfully retrieved I call exchangeCodeForToken():

const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
const data = {
grant_type: AuthSession.GrantType.AuthorizationCode,
code: code,
redirect_uri: SPOTIFY_REDIRECT_URI1,
client_id: SPOTIFY_CLIENT_ID,
code_verifier: code_verifier,    // stored and then retrieved from local storage
};

await axios.post(
new URLSearchParams(data),
{ headers },
).then((response) => {
const tokenData = response.data;
setAccessToken(tokenData.access_token);
fetchUserProfile(tokenData.access_token);
}).catch((error) => {
throw new Error(`Token exchange failed: ${error}`);
});
 
So far, I successfully get redirected to spotify's authorization page, but when I click agree I get an error and do not receive a token. I tried executing a curl request with the authorization code I receive in the first authentication step within terminal as well, but I get the error 'code_verifier was incorrect'. I am failing to understand what I am doing wrong, but it seems to be something with the code verifier or my code challenge. If anyone has any ideas, please I would love some help and guidance. Thanks!
Reply
0 Replies

Suggested posts