Missing required parameter [code_verifier]
await authClient.requestAccessToken(code)
crashes with
error: {
error: 'invalid_request',
error_description: 'Missing required parameter [code_verifier].'
}
I think the problem is that #codeVerifier is undefined here 👇
https://github.com/twitterdev/twitter-api-typescript-sdk/blob/0d4954c675dbfc566c6911adc4d4178dce926ca4/src/OAuth2User.ts#L170
I am having this exact same issue. The only thing I can think is that the oAuth has a different code_verifier value than my redirect URL for the callback-->both use new auth.OAuth2User to create a client for use on either oAuth or the callback (which is slightly different than what the example code is doing in an app). This is just speculation though.
Thanks @RodionChachura, you need to call generateAuthURL to create the code_verifier. Will add a check and throw a helpful error to improve this
@wmencoder thats right, you need to use the same Auth Client that generated the authentication URL
I am having the same issue, but this solution will not work for me. Is there a way to generate an access token statelessly? My app runs in a serverless environment and I can't persist an instance of the OAuth2User class between requests. I can store any data necessary, but the code_verifier property is private and it can't be accessed from outside the class.
EDIT: I have resolved the issue by passing my own code_challenge and setting the code_challenge_method as 'plain'. I then store the code_challenge and generate another authURL with the same code_challenge before generating the access token. I hope that helps if anyone else is in the same situation.
@jgjr I am also trying to use this SDK in a serverless environment. Could you please a minimal working code for the same?
@refarer An official example to run this SDK on serverless environment would be really appreciated.
@sasivarnan The solution was fairly simple. In the initial call of generateAuthURL() I use code_challenge_method: 'plain' and save the code_challenge that I use. Then when the user is redirected back to my platform I call the generateAuthURL() method again with the same saved code_challenge, and then the requestAccessToken() method with the code I have received.
@sasivarnan I was able to generate a stateless client by creating a class
class OAuth2UserStateless extends auth.OAuth2User
That overloads the constructor and assigns the Token that I pass as a cookie from the client. You can theoretically do the same with the code_verifier property instead of doing void call to generateAuthURL to populate that property.
This is a hack and clearly this SDK isn't designed for statelessness at this time.
@sasivarnan The solution was fairly simple. In the initial call of generateAuthURL() I use code_challenge_method: 'plain' and save the code_challenge that I use. Then when the user is redirected back to my platform I call the generateAuthURL() method again with the same saved code_challenge, and then the requestAccessToken() method with the code I have received.
Feels crazy hacky but works
I created a PR to solve this issue, I need to work with the maintainers to get a code review and eventually this feature can be merge, the PR is here if you want to take a look:
https://github.com/twitterdev/twitter-api-typescript-sdk/pull/42
The 1.2.0 version published 6 days ago (thanks @refarer!) allows the token to be passed on the constructor. So, now you could do something like this:
- Create the endpoint to start the authentication process: generate
stateandchallengeand callgenerateAuthURL; persist these values to recreate the OAuth2User later on; - Create another endpoint for the callback: recreate the auth with
stateandchallengeand callrequestAccessTokenpassing thecodereceived; store the token returned by that function; - Pass the token on the OAuth2UserOptions during user creation.
Using firebase functions, my simplified code is:
// authenticate.ts
export const authenticate = functions
.region('southamerica-east1')
.https.onRequest(async (req, res) => {
res.redirect(await generateAuthURL());
});
// authenticationHandler.ts
export const authenticationHandler = functions
.region('southamerica-east1')
.https.onRequest(async (req, res) => {
const { code } = req.query;
await handleAuthCode(code as string);
res.send('OK');
});
// auth.ts
let user: auth.OAuth2User | null = null;
const getUser = async () => {
if (!user) {
const { token } = (await getPlatformTokens()) ?? {};
user = new auth.OAuth2User({
client_id: <CLIENT_ID>,
client_secret: <SECRET>,
callback: <CALLBACK_URL>,
scopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
token: token ? JSON.parse(token) : undefined,
});
}
return user;
};
let client: Client | null = null;
const getClient = async () => {
if (!client) client = new Client(await getUser());
return client;
};
export const generateAuthURL = async () => {
const state = randomBytes(12).toString('hex');
const challenge = randomBytes(12).toString('hex');
await updatePlatformTokens({
state,
challenge,
});
const user = await getUser();
return user.generateAuthURL({
state,
code_challenge_method: 'plain',
code_challenge: challenge,
});
};
export const handleAuthCode = async (code: string) => {
const user = await getUser();
const { state, challenge } = (await getPlatformTokens()) ?? {};
if (state && challenge) {
user.generateAuthURL({
state,
code_challenge_method: 'plain',
code_challenge: challenge,
});
const { token } = await user.requestAccessToken(code);
await updatePlatformTokens({
token: JSON.stringify(token),
});
}
};
@pdandradeb what is getPlatformTokens and it's type?
@apecollector
// types.ts
export interface PlatformTokens {
token?: string;
state?: string;
challenge?: string;
}
// tokens.ts
export const getPlatformTokens = async () => {
const tokens = await getFirestore()
.collection('platform')
.doc('tokens')
.get();
return tokens.data() as PlatformTokens;
};
export const updatePlatformTokens = async (tokens: Partial<PlatformTokens>) => {
await getFirestore()
.collection('platform')
.doc('tokens')
.set(tokens, { merge: true });
};
// authenticate.ts export const authenticate = functions .region('southamerica-east1') .https.onRequest(async (req, res) => { res.redirect(await generateAuthURL()); });
My implementation is almost exactly the same except using an onCall instead of on request, in hopes of limiting the ability for this function to be called from any browser.
This thread was super helpful! Got me past a few roadblocks while trying to use this sdk with nextjs and serverless api functions
This SDK is very problematic. In particular, the inability to inject code_verifier from the outside should be fixed as soon as possible. I'm extending it with a Service class that has a constructor that can inject the code_verifier and code_challenge from the outside. I hope this implementation helps those who are having trouble. Also, if there are any problems, please point them out.
https://gist.github.com/amemiya/14f1614819210b8be5d4afcbf4727ca6