I am currently trying to build a Twitter authorisation flow for a React Native (Expo) application using Twitter’s v2 API and expo-auth-session. I have been able to receive a code from Twitter’s API after authenticating the user, but I am running into a problem exchanging the code for an access token as per the documentation. I have built a React hook to handle the entire Twitter auth process, but after login, I always get this error
Value passed for the code verifier did not match the code challenge
Here is a snippet of my code
import React from "react"
import {
useAuthRequest,
CodeChallengeMethod,
ResponseType,
makeRedirectUri,
} from "expo-auth-session"
import { SocialLogins, SOCIAL_TOKENS, useGlobalStore } from "@libs/shared"
import { TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET } from "@env"
import pkceChallenge from "react-native-pkce-challenge"
const { codeChallenge, codeVerifier: code_verifier } = pkceChallenge()
export const useTwitterAuth = () => {
const { socialAuth, handleSocialAuth, services } = useGlobalStore()
const discovery = {
authorizationEndpoint: "https://twitter.com/i/oauth2/authorize",
tokenEndpoint: "https://twitter.com/i/oauth2/token",
revocationEndpoint: "https://twitter.com/i/oauth2/revoke",
}
const state = "my-state"
const [, , promptAsync] = useAuthRequest(
{
clientId: TWITTER_CLIENT_ID!,
clientSecret: TWITTER_CLIENT_SECRET,
// TODO: Refactor
redirectUri: makeRedirectUri({ scheme: "scheme" }),
usePKCE: true,
state,
scopes: ["tweet.read", "users.read", "offline.access"],
responseType: ResponseType.Code,
codeChallengeMethod: CodeChallengeMethod.S256,
codeChallenge,
},
discovery
)
const handlePressAsync = async () => {
const result = await promptAsync()
if (result.type !== "success") {
alert("Uh oh, something went wrong")
return
}
const accessCode = result.params?.code as string
const body = new URLSearchParams({
code: accessCode,
grant_type: "authorization_code",
client_id: TWITTER_CLIENT_ID!,
state,
redirect_uri: makeRedirectUri({ scheme: "scheme" }),
code_verifier,
}).toString()
fetch("https://api.twitter.com/2/oauth2/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body,
})
.then((resp) => resp.json())
.then((data) => console.log("DATA =>", data))
handleSocialAuth(SocialLogins.Twitter, true)
await services.storage.setSocialToken(SOCIAL_TOKENS.Twitter, accessCode)
}
const handleAuth = async () => {
if (socialAuth[SocialLogins.Twitter]) {
handleSocialAuth(SocialLogins.Twitter, false)
await services.storage.removeSocialToken(SOCIAL_TOKENS.Twitter)
} else {
handlePressAsync()
}
}
React.useEffect(() => {
const handleHasTwitterUser = async () => {
const twitterToken = await services.storage.getSocialToken(
SOCIAL_TOKENS.Twitter
)
if (!twitterToken) {
return
}
handleSocialAuth(SocialLogins.Twitter, true)
}
handleHasTwitterUser()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return {
handleTwitterAuth: handleAuth,
}
}
This STILL returns the following error:
Value passed for the code verifier did not match the code challenge
I have checked that the react-native-pkce-challenge dependency I have used is working, by checking that the code challenge hasher creates the exact same code challenge as the dependency, and it seems to work as expected.
If anyone has experienced this issue, I am open to hearing from you. Thanks!
I tried to get the user to authorize my app to act on their behalf as a Twitter user. I have successfully received an access code, but I am not able to convert the access code to a token using PKCE, because even though it is correct, I still receive the same error.
Value passed for the code verifier did not match the code challenge
2
Answers
I had the same issue, just fixed it by using the codeVerifier contained in the request returned by the hook
useAuthRequest()
instead of creating a new one withreact-native-pkce-challenge
.Above answer helped me in resolving below error
OAuth "Facebook Platform" "invalid_code" "This authorization code has been used
Instead of using codeVerifier from saved state in react native , I used it from request object returned by hook useAuthRequest()
const [request, result, promptAsync, dismiss] = AuthSession.useAuthRequest(// rest of the code)
Use it like this to print before trying to fetch access token
console.log(‘code verifier is ‘, request.codeVerifier);
hope this helps.