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

Update parameters-whitelist.js to include access_type and approval_prompt #566

Closed
johnlim opened this issue Nov 17, 2017 · 8 comments
Closed

Comments

@johnlim
Copy link

johnlim commented Nov 17, 2017

Hi,

When calling the authorize endpoint with access_type and approval_prompt, the following warning is printed to console Following parameters are not allowed on the `/authorize` endpoint: [access_type, approval_prompt].

However, these are valid parameters for connections such as google-oauth2. I'd be happy to update parameters-whitelist.js and issue a pull request but wanted to run this by you guys first.

Cheers,
John

@aaguiarz
Copy link
Contributor

Hi,

Can you give us some background on why you need those?

None of them are standard OAuth/OIDC parameters, and we don't want customers to send parameters to /authorize that are not in the standard.

approval_prompt seems to have been replaced with the prompt=none:
googleapis/oauth2client#453 (comment), which we support.

The approach we'd want to take here is to understand the use case and have our server do the right mapping between standard parameters to /authorize and whatever upstream identity providers like google expect. For example, access_type is used for offline access, and the OIDC standard says it should be set in the 'scope' parameter.

Thanks,

Andres

@johnlim
Copy link
Author

johnlim commented Nov 18, 2017

Hi @aaguiarz , definitely. My application gives users the option to link their google accounts. When they link their accounts, my app makes a call to the authorize endpoint by setting connection to google-oauth2 like so

//using  auth0.js ....
        var options = {
          scope: 'openid',
          connection: 'google-oauth2',
          redirectUri: myRedirectUri,
          responseType: 'token',
          accessType: 'offline',
          approvalPrompt: 'force',
          login_hint: this.email
        };

        var webAuth = new auth0.WebAuth({
          domain: this.auth0.domain,
          clientID: this.auth0.clientId
        });

        webAuth.authorize(options);

This will "force" google to return me a refresh token which my app needs. Without setting access_type and approval_prompt, google does not return me the required refresh token (at least not on subsequent auth).
The current Auth0 Authentication Api documentation specifies sending these extra params in additional-parameters but auth0.js also does not support additional-parameters as well.
Passing in access_type and approval_prompt in options is the only way it will work but it returns the warning message.

I hope the above provides more context but please let me know if you require further information.

More details: I just tried with

...
        var options = {
          scope: 'openid offline',
          connection: 'google-oauth2',
          redirectUri: myRedirectUrl,
          responseType: 'token',
          prompt: 'consent',
          login_hint: this.email
        };
...     

but the refresh token is still not being returned.
However, explicitly setting accessType: 'offline' with prompt: 'consent' works though. Do we need to support accessType in the whitelist or did I use the scope wrongly? Thanks.

P.S: I seem to recall that Auth0 dashboard had an offline permission for Google connections before but I cant find it anymore. Maybe re-enabling that feature there instead is an option?

Regards,
John

@lordnox
Copy link
Contributor

lordnox commented May 18, 2018

I just opened a pull request #760
This just adds access_token and display to the whitelisted params like described here:
https://auth0.com/docs/api/authentication?javascript#social

@luisrudge
Copy link
Contributor

Hi! I'm sorry about the copy/paste message, but I'm cleaning up some stale issues. 🗑
It's been a while since this issue was opened. Are you still having this issue with the latest version? If so, please reopen it with some repro steps so we can help!

@morphatic
Copy link

I just tried to get a refresh_token for a google-oauth2 connection using the method described in the docs and above.

On the frontend I'm using auth0-js with the following config:

import auth0 from 'auth0-js'

const auth0 = new auth0.WebAuth({
    domain: 'https://example.auth0.com',
    clientID: 'my_client_id',
    redirectUri: 'https://example.com/handle-auth',
    audience: 'https://example.auth0.com/api/v2/',
    responseType: 'id_token token',
    scope: 'openid profile email read:current_user create:current_user_metadata update:current_user_metadata',
    accessType: 'offline' // <-- this is supposed to trigger refresh token retrieval, I think
})

My backend API uses a separate machine-to-machine Auth0 app that has access to the Management API with the following scopes:

read:users
update:users
delete:users
create:users
read:users_app_metadata
update:users_app_metadata
delete:users_app_metadata
create:users_app_metadata
create:user_tickets
read:user_idp_tokens

The backend client is configured and queries Auth0 as follows:

// config
const mc = require('auth0').ManagementClient

const client = new mc({
  domain: 'example.auth0.com',
  clientId: 'my_client_id',
  clientSecret: 'my_client_secret'
})

// queries, e.g.
client.getUsersByEmail('[email protected]')
client.getUser({ id: 'google-oauth2|1234567890' })

I've also tried using the API Explorer but no matter which method I try, the identity object that comes back looks like this:

{
  access_token: "a_valid_access_token_12341341234",
  connection: "google-oauth2",
  expires_in: 3600,
  isSocial: true,
  provider: "google-oauth2",
  user_id: "1234567890"
}

No refresh token. 😢

Am I missing something or doing something wrong?

The reason this is important is that, as noted above, the Google API access_token expires after only one hour. AFAICT, the only way to refresh this token is to actually log out and log back in again. Unfortunately, just re-requesting the user's info from /api/v2/users/[user_id] does not get a newly refreshed access_token from the IdP.

Does this make sense?

@johnlim
Copy link
Author

johnlim commented May 28, 2019

@morphatic It's been a while since I last touched this but iirc, specifying accessType=offline only returns a refresh token on the very first login. Can you try "signing up" with a new user and see if google returns you a refresh token?

@morphatic
Copy link

morphatic commented Jun 14, 2019

@johnlim Thanks for the feedback! It took a couple of weeks for this to get back to the top of my "todo" list. I was finally able to get a refresh_token!

As you indicated, you only get a refresh_token on the occasion of the very first login. Here are the steps I took to re-create that experience:

  1. Remove access to your app from your Google account:
    1. Go to https://myaccount.google.com and log into your Google account
    2. Go to Security (lefthand menu)
    3. Scroll down to the "Third-party apps with account access" section and click the link to "Manage third-party access"
    4. On the page that comes up, find your app, click on it, and then click the "Remove Access" button. This will delete your Auth0 app from the list of "approved" apps.
  2. Remove your user account from Auth0:
    1. Log into the Auth0 dashboard for your app and navigate to Users & Roles > Users
    2. Delete the user account. ⚠️ CAUTION: you WILL lose all data associated with this user when you delete their account.
  3. Make sure you're logged out of your own app (the one you're building) and that you've cleared all browser data away before "signing up" again.
  4. With any luck, on this attempt, you'll be sent a refresh_token for your Google OAuth2 login.

I ended up writing a rule that will store the refresh_token in user.app_metadata. I did this because there's too much chance that the refresh_token might get lost in transit if I waited until it got to the frontend before trying to capture it. Here's the code I used in my rule (NOTE: 🚨 This is a BAD IDEA™, I do NOT recommend anyone do this):

/**
 * Store the user's Google API tokens in app_metadata.
 */
function (user, context, callback) {

  // get the full Google identity record
  const identity = user.identities.filter(i => i.provider === 'google-oauth2')[0];

  // abort if there is no Google identity
  if (!identity) callback(null, user, context);

  // otherwise, make sure app_metadata is defined
  user.app_metadata = user.app_metadata || {};

  // make sure that the "google" property is defined
  user.app_metadata.google = user.app_metadata.google || {};

  // if we were passed a refresh token... (only happens on 1st login?)
  if (identity.refresh_token) {
    // add it to the metadata
    user.app_metadata.google.refresh_token = identity.refresh_token;
  }

  // update the access token in the metadata (should be passed every login)
  user.app_metadata.google.access_token = identity.access_token;

  // update the app_metadata in the Auth0 database
  auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
    .then(() => {
      // move along...
      callback(null, user, context);
    })
    .catch(err => {
      // ruh-roh! couldn't update app_metadata
      callback(err);
    });
}

🚨 BAD IDEA™ Do NOT Do This

This works as far as getting access to the token, but this is a BAD IDEA since it will end up sending the refresh_token to the client when people log in. Since my app stores their current Auth0 user info in local storage (to allow logins to be refreshed in the background), that would mean the refresh_token is stored in the clear in the browser where other actors might gain access to it. While all of my clients use HTTPS, you should never store the refresh_token in browser clients.

Better Idea, but not 100% reliable

As an alternative, instead of storing the refresh_token in app_metadata, I guess I could instead send it to my backend API and store it there. That would allow me to implement an API endpoint that could retrieve a new access_token for the Google API whenever necessary without ever having to send the refresh_token to any client. My fear with this one is that you only get one chance to capture the refresh_token and if the API call from Auth0 to my backend API fails for some reason, then I'm stuck without being able to refresh the user's access_token short of forcing them to log out of my app entirely and log back in.

Of course, all of these issues would go away if there were some way to get an updated Google access_token on demand via Auth0 without forcing users to logout and then log back in again. I feel like there should be some way to do this, but I'm having trouble figuring it out.

Any thoughts?

@morphatic
Copy link

Okay, I think I've come up with a solution for this. I wrote a blog post about it. Feedback is welcome!

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

No branches or pull requests

5 participants