aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.4k stars 2.11k forks source link

Auth.configure refresh access token for OpenId providers #4782

Open tomasyaya opened 4 years ago

tomasyaya commented 4 years ago

Which Category is your question related to?

Authentication

Amplify CLI Version

4.12

What AWS Services are you utilizing?

Cognito GraphQl Api

Provide additional details e.g. code snippets

Hello.

Im currently doing Auth through openId with salesforce.

Im retrieving the access token, refresh token an profile info and getting AWS credentials through Federated Sign In.

At some point my credentials expire. Because Amplify does not automatically refresh access token for salesforce (I read it does for Amazon, Google and Facebook) Im required to present a callback that retrieves the new access token.

Amplify Example


import { Auth } from 'aws-amplify';

function refreshToken() {
    // refresh the token here and get the new token info
    // ......

    return new Promise(res, rej => {
        const data = {
            token, // the token from the provider
            expires_at, // the timestamp for the expiration
            identity_id, // optional, the identityId for the credentials
        }
        res(data);
    });
}

Auth.configure({
    refreshHandlers: {
        'developer': refreshToken // the property could be 'google', 'facebook', 'amazon', 'developer', OpenId domain
    }
})

And this is what Im doing:

export async function retreiveAccessToken(refreshToken) {
  try {
    const { data } = await axios.post(`${refreshTokenUrl(refreshToken)}`);
    const rawUser = JSON.parse(atob(data?.idToken.split(".")[1]));
    return new Promise(res => {
      const user = {
        // eslint-disable-next-line camelcase
        token: data?.access_token, // the token from the provider
        expires_at: rawUser?.exp // the timestamp for the expiration
      };
      res(user);
    });
  } catch (e) {
    console.log(e, "error");
  }
}
Auth.configure({
      refreshHandlers: {
        developer: retreiveAccessToken.bind({}, inputObj.refreshToken)
      }
    })

This fails to refresh the access token, credentials expire and im not able to access my GraphQl api.

Some pointers:

The refresh token endpoint in Salesforce does not support CORS. For testing purpose I fetch the new access token through a Lambda function (and it works perfect). But I didn't use it in the Auth.configure because to access it you new a valid access token and that wont be the case when the lambda is going to be execute. The callback I hook into the Auth.configure does not work from web because of CORS. But I believe Amplify runs it in node environment so it shouldn't be a problem (i guess).

Please, if someone more experience would lend me a hand that would be great. Let me know is im not being clear with something. Thank you very much

tomasyaya commented 4 years ago

Hello.

I have created a proxy so I can call my refresh token from the front end, in case thats the environment from where amplify triggers the callback.

The proxy itself works fine, if I trigger the callback from the web its fetches the the data OK.

Nevertheless when added to the Auth.configure, at some point the credentials expire and there is no refresh being done.

Some corrections I did to my code are:

In the return obj I now return id_token and not access token, because as im using OIDC amplify need the id_token (either way I tested with access token and it does not work).

The key in the Auth.config is now the domain ("login.salesforce.com") and not developer

Auth.configure({ refreshHandlers: { login.salesforce.com: retreiveAccessToken.bind({}, inputObj.refreshToken) } })

I also tried returning a plain obj instead of a promise, because the callback is async and returns a promise either way but this didn't work either.

If you have some clarifications please send them in

Thank you

tomasyaya commented 4 years ago

Another thing, I don't know if its related but:

After calling Auth.configure({ refreshHandlers: ... }) if I call Auth.currentAuthenticatedUser() the refreshCallbacks array is empty, does it has anything to do?

Maybe the Auth.config is not persisting?

bneigher commented 4 years ago

I'm struggling with this, federated with auth0... empty refreshCallbacks.. react-native

const refreshToken = require('refreshser')

Amplify.configure({
  Auth: {
    auth0: {
      domain: Config.AUTH0_DOMAIN,
      clientID: Config.AUTH0_CLIENT_ID,
      redirectUri: '/',
      audience: `${Config.AUTH0_AUDIENCE}/userinfo`,
      responseType: 'token id_token', // for now we only support implicit grant flow
      scope: 'openid offline_access profile email', // the scope used by your app
      returnTo: '/',
    },
    mandatorySignIn: false,
    region: Config.AWS_REGION,
    identityPoolRegion: Config.AWS_REGION,
    identityPoolId: Config.AWS_COGNITO_ID_POOL,
    userPoolId: Config.AWS_USER_POOL,
    userPoolWebClientId: Config.AWS_USER_POOL_CLIENT,
    refreshHandlers: {
      [Config.AUTH0_DOMAIN]: refreshToken, // ****.auth0.com
      developer: refreshToken,  // testing
    },
  }
})
BrianHHough commented 1 year ago

Has anyone been able to figure this out, especially the audience part? @bneigher @tomasyaya

I have been able to integrate Cognito/OIDC/Auth0, but the ability to get a data payload that isn't an opaque token (empty data payload) has been a major struggle bus. It is referenced in this forum post: https://community.auth0.com/t/why-is-my-access-token-not-a-jwt-opaque-token/31028

Really curious if anyone has found a way to reconcile the access of AWS resources and Auth0 resources either as 1 combined jwt, or an AWS-authorized jwt, and then a client-side fetched Auth0 access_token?