Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing required parameter [code_verifier] #25

Open
radzionc opened this issue Jun 23, 2022 · 15 comments
Open

Missing required parameter [code_verifier] #25

radzionc opened this issue Jun 23, 2022 · 15 comments

Comments

@radzionc
Copy link

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

@wmencoder
Copy link

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.

@refarer
Copy link
Collaborator

refarer commented Jun 30, 2022

Thanks @RodionChachura, you need to call generateAuthURL to create the code_verifier. Will add a check and throw a helpful error to improve this

@refarer
Copy link
Collaborator

refarer commented Jun 30, 2022

@wmencoder thats right, you need to use the same Auth Client that generated the authentication URL

@jgjr
Copy link

jgjr commented Jul 12, 2022

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.

@sasivarnan
Copy link

sasivarnan commented Jul 30, 2022

@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.

@jgjr
Copy link

jgjr commented Aug 2, 2022

@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.

@mathieuhelie
Copy link

@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.

@elie222
Copy link

elie222 commented Aug 13, 2022

@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

@danforero
Copy link

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:

#42

@pdandradeb
Copy link

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:

  1. Create the endpoint to start the authentication process: generate state and challenge and call generateAuthURL; persist these values to recreate the OAuth2User later on;
  2. Create another endpoint for the callback: recreate the auth with state and challenge and call requestAccessToken passing the code received; store the token returned by that function;
  3. 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),
    });
  }
};

@apecollector
Copy link

@pdandradeb what is getPlatformTokens and it's type?

@pdandradeb
Copy link

@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 });
};

@PMLyf
Copy link

PMLyf commented Oct 21, 2022

// 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.

@jacklynch00
Copy link

This thread was super helpful! Got me past a few roadblocks while trying to use this sdk with nextjs and serverless api functions

@amemiya
Copy link

amemiya commented Dec 28, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.