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

How to make this library work with multiple user accounts? #2080

Closed
ParadisusParadoxum0000 opened this issue May 5, 2020 · 11 comments
Closed
Assignees
Labels
needs more info This issue needs more information from the customer to proceed. type: question Request for information or clarification. Not an issue.

Comments

@ParadisusParadoxum0000
Copy link

Hi, can someone please give me some pointers on how this can work with multiple users?

The aim of my project is to access my users' accounts and do something on their behalf. If I'm understanding this right(please correct me if I'm wrong), I create an OAuth2Client with my project's client id, client secret and redirect url, then with this OAuth2Client I generate an auth url which my users will access to give permission and an auth code per user will be returned to my redirect url. How do I identify which user's auth code is returned? Also once I have received all my users' access and refresh tokens, how do I refresh multiple users' access tokens? There is the method OAuth2Client.setCredentials which allows me to set the refresh token of a single user, and with an event listener I can get a new access token when the previous expired, but how do I use setCredentials for each refresh tokens of all my users?

Any help will be much appreciated, thanks in advance!

@yoshi-automation yoshi-automation added the triage me I really want to be triaged. label May 6, 2020
@oliverjessner
Copy link

"he aim of my project is to access my users' accounts and do something on their behalf."
Not sure if this is allowed.

Access tokens are automatically refreshed

@ParadisusParadoxum0000
Copy link
Author

I've phrased it badly, everything will still be done with the users' permission and out of their own action.

I understand that by calling OAuth2Client.setCredentials with a single user's refresh token will allow a new access token to be generated automatically when the previous one expires, but the question here is how do I deal with multiple refresh tokens from different users with the same OAuth2Client? For example would it still work if I call OAuth2Client.setCredentials(user1RefreshToken) then call OAuth2Client.setCredentials(user2RefreshToken)? But then if this does work, when a new access token is generated, how do I know which user the new access token belongs to?

My other question is, when the users sign in through the auth url generated, auth codes will be sent to the redirect url, how do I identify which auth code belongs to which user when all that is sent to the redirect url is the auth code with no other information to identify the users with?

Thanks for your time

@sofisl sofisl added status: investigating The issue is under investigation, which is determined to be non-trivial. type: question Request for information or clarification. Not an issue. labels May 6, 2020
@yoshi-automation yoshi-automation removed the triage me I really want to be triaged. label May 6, 2020
@sofisl sofisl removed the status: investigating The issue is under investigation, which is determined to be non-trivial. label May 11, 2020
@JustinBeckwith
Copy link
Contributor

Hey folks! I will admit doing this right is a tad tricky. I highly reccomened using a distinct OAuth2Client object for each user credential (access token and refresh token) being stored. After the user logs in, and the call comes back with credentials to your endpoint I would:

  • Instantiate a new OAuth2Client using the keyfile you've downloaded (same for everyone)
  • Use setCredentials to store the appropriate access token and refresh token
  • Store the OAuth2 object in a map, using the userId as a key

The OAuth2 objects are stateful (for better or for worse). They will manage refreshing the access token automatically for you assuming there is a refresh token in place. If you were to try using a single object and switch contexts, there is too much risk that you'll leak credentials meant for one user to another user. Then, very bad things happen.

By keeping these in separate stateful objects in a map, there's far less risk of cross-contaminating keys. Additionally, if you choose to store your access and refresh token securely, it makes rehydrating them later a fair bit easier.

This is all more complex than I'd like it to be :/ Does this explanation make sense?

@JustinBeckwith JustinBeckwith added the needs more info This issue needs more information from the customer to proceed. label May 24, 2020
@ParadisusParadoxum0000
Copy link
Author

Thanks for the reply, having a separate OAuth2Client for each user would indeed solve the access token refreshing problem when there are multiple users.

Can you please shed some light on how I can identify which user the credentials belong to as well? Currently when the user logs in and tokens are returned to my endpoint there are only 2 query parameters; "code" and "scope", when multiple users sign in at unknown times and in an unknown order, how can I tell which auth code belongs to which user?

@JustinBeckwith
Copy link
Contributor

It took me longer to figure that out than I'd care to admit 😆 When handling the request to your redirect URI, you will get a code, which you can exchange for tokens. You will get a few tokens back:

  • access_token
  • id_token
  • refresh_token
  • etc...

You can actually get the user email from both the access_token and the id_token! For today, let's just do the access_token. You are gonna do something like this:

const r = await oAuth2Client.getToken({
  code: code,  
  codeVerifier: codes.codeVerifier,
});
console.log(r.tokens);

const tokenInfo = await oAuth2Client.getTokenInfo(r.tokens.access_token);
console.log(tokenInfo.email);

You could also snag it from the id_token like this:

const ticket = await oAuth2Client.verifyIdToken({
  idToken: r.tokens.id_token,
  audience: keys.web.client_id,
});
console.log(ticket);

I believe to ensure email comes back in either of those calls, you have to be careful to include the https://www.googleapis.com/auth/userinfo.email scope in your call to generateAuthUrl.

Give this a try and let me know how it goes!

@ParadisusParadoxum0000
Copy link
Author

Just tried it and both the solutions you provided works, thanks so much for the help!

@kbyatnal
Copy link

kbyatnal commented Oct 14, 2020

@JustinBeckwith I think you should add I highly reccomened using a distinct OAuth2Client object for each user credential (access token and refresh token) being stored. to the README. I'm sure there are many people that are doing this incorrectly today based on the examples, and like you noted, it's a huge security risk.

I only found this because as soon as I saw .setCredentials being called on a global object, I knew something was off so I went looking. But others may not do so.

@shoaib30
Copy link

@JustinBeckwith your solution for getting the email was a saviour. it should definitely be added to the docs. Stumbled upon this here without expecting it.

@agoyal0422
Copy link

What exactly is a "keyfile?"

@ParadisusParadoxum0000
Copy link
Author

@agoyal0422 The Client Id and Client Secret that you use to instantiate a new OAuth2Client can be downloaded into a JSON file and that file is the keyfile. And in case if you're unsure where you can get the Client Id and Client Secret they can be found in the Credential section of your project in the API dashboard (https://console.cloud.google.com/apis)
keyfile

@Helveg
Copy link

Helveg commented Jan 11, 2023

What the whole map approach doesn't take into account is that it can end up costing a whole lot of memory to store one object per user...


PS: Another trick to link authorization codes to users is to use the state parameter, and to send along whichever encrypted data you may need when the authorization code comes back:

    oAuth2Client.generateAuthUrl({
      access_type: 'offline',
      prompt: 'consent',
      state: someWayOfEncrypting(userId)
    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs more info This issue needs more information from the customer to proceed. type: question Request for information or clarification. Not an issue.
Projects
None yet
Development

No branches or pull requests

9 participants