Expo AuthSession: PKCE always leads to invalid code verifier

Please provide the following:

  1. SDK Version: 44
  2. Platforms(Android/iOS/web/all): all

I’ve been trying to get the PKCE flow to work with AuthSession, but I always seem to get back some form of “invalid code verifier” in the token exchange.

The first request works well. It looks something like this (using Spotify as an example):

let [spotifyRequest, spotifyResponse, spotifyPrompt] = AuthSession.useAuthRequest({
    clientId: Constants.manifest.extra.SPOTIFY_CLIENT_ID,
    codeChallenge: pkcePair.pkceCodeChallenge,
    codeChallengeMethod: AuthSession.CodeChallengeMethod.S256,
    redirectUri: AuthSession.makeRedirectUri({ path: "callback/" }),
    responseType: AuthSession.ResponseType.Code,
    scopes: ["playlist-modify-public"],
    state: props.oAuthState
}, {
    authorizationEndpoint: "https://accounts.spotify.com/authorize",
    tokenEndpoint: "https://accounts.spotify.com/api/token"
});

Sweet! I get a code back from Spotify. However, when I then go to exchange that code for an access token, I always get “invalid code verifier” back. I’ve tried this with Snapchat and Spotify. I’ve verified my code challenge and code verifier against a number of sources. Here’s an example of a PKCE pair (using expo-crypto and expo-random):

Code challenge: upcekQ41tUNTRrB87jC8ooW5hQDeuSV2O10SWbn7670
Code verifier: 0PTx8xHr_9-aAfB9SS4alJ00_wgErRW-qwOAMJJMzg4

Can it be in the way AuthSession passes in the code challenge? Any suggestions?

Encouragingly, if I cut out my backend altogether and rely entirely on expo-auth-session, the code verifier is still rejected:

tokenResponse = await AuthSession.exchangeCodeAsync({
clientId: [redacted],
clientSecret: [redacted],
code: spotifyResponse.params.code,
extraParams: {
code_verifier: pkcePair.pkceCodeVerifier
},
redirectUri: AuthSession.makeRedirectUri({ path: “callback/” })
}, {
tokenEndpoint: “https://accounts.spotify.com/api/token
});

So unless I’m reading the code incorrectly, AuthRequest overwrites the codeChallenge I give it unless I provide a codeVerifier too. That’s… unfortunate. How do I do that in AuthSession.useAuthRequest?

That said, I’m not crazy, so that’s something.