adopted-ember-addons / ember-cognito

AWS Amplify/Cognito and ember-simple-auth integration
MIT License
32 stars 25 forks source link

Access Token not refreshed #23

Closed garethbradley closed 6 years ago

garethbradley commented 7 years ago

Hi,

I'm not sure if this issue is ember-cognito, simple-auth or my own code, so apologies for this "newbie" issue.

Problem I have a RESTAdapter setup with a customer authorizer:

// app/adapters/application.js
import DS from 'ember-data';
import ENV from '../config/environment';
// import Ember from 'ember';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';

export default DS.RESTAdapter.extend(DataAdapterMixin, {
  namespace: ENV.APP.apiNamespace,
  host: ENV.APP.apiURL,
  authorizer: 'authorizer:application'
});
// app/authorizers/application.js
import Base from 'ember-simple-auth/authorizers/base';
const { isEmpty } = Ember;

export default Base.extend({

  authorize(data, block) {
    const accessToken = data['access_token'];

    console.log('authorize', data);

    if (!isEmpty(accessToken)) {
      block('Authorization', `${accessToken}`);
    }
  }
});

When I make calls to my API, the authorization header is set correctly, and everything works as expected. If I leave the app for a period of time (assuming more than an hour, but not tested yet), I get a 401 back. When that happens, the Ember page doesn't load and I get an error in the console.

Expectation I understand that I need to use the refresh token to request a new access token, but I'm struggling to see how to do that. I have no record of the expiry time (doesn't seem to be passed back).

I can't decode the access code to grab the claims as those keys would be available in this client side application.

Any ideas how I can do this without requiring the user to log out / log in?

Many thanks!

paulcwatts commented 7 years ago

Hi @garethbradley, thanks for filing this issue! This is still a relatively new project, so I assume there are still some bugs to be found.

You are using ember-cognito's authenticator, correct? ember-cognito's authenticator (and the underlying amazon-cognito-identity-js package) should handle the refresh token automatically.

That said, I have noticed in one of my apps that the authenticator's restore method will intermittently throw an exception. I haven't been able to see the stack trace or the reason, because ember-simple-auth will immediately invalidate the session and refresh the browser ☹️

It's been on my plate to investigate that, and try to record the exception, I just haven't had time recently. I'll try to investigate it sometime soon. If you see that exception and can get me that stack trace, it would help me significantly 👍

paulcwatts commented 7 years ago

By the way, you can access the access token's expiration in a bit of a round about way. You can get it through the cognito service:

cognito: service(),
...
this.get('cognito.user').getSession().then((session) => {
   console.log(session.getAccessToken().getExpiration());
}

You may not be able to do this in the authorizer's authorize method (because it requires a promise), but this allow you to tell whether the token is expired.

Also, I should mention that ember-cognito does not automatically refresh the token when it expires. It only does that in the authenticator's restore method, which is generally only called on app start. This is somewhat of a limitation of ember-simple-auth.

adet4ever commented 4 years ago

Hi there,

Any ideas as to how to refresh the idToken?

paulcwatts commented 4 years ago

Hi @adet4ever , you can add autoRefreshToken: true to the addon's config and all tokens will get refreshed when they expire: https://github.com/paulcwatts/ember-cognito#optional-configuration

adet4ever commented 4 years ago

Hi @paulcwatts I added the configuration for autoRefreshToken: true but im still getting the error: JWT::ExpiredSignature (Signature has expired):

here is how I am getting the idToken from my service:

  id_token() {
    return this.cognito.auth.user.signInUserSession.idToken.jwtToken;
  }

This is primarily because I need to include the idToken() in the header on the application adapter. getIdToken() as specified in the docs returns a promise.

paulcwatts commented 4 years ago

Hi @adet4ever , sorry for taking sooo long to get back to you.

If you haven't figured this out yet (or moved on), you have to get the session first, that's the sure-fire way of getting a current token. For instance, in your adapter you can do something like this:

  @computed("accessToken")
  get headers() {
    const { accessToken } = this;
    if (accessToken) {
      return { Authorization: `Bearer ${accessToken}` };
    } else {
      return {};
    }
  }

  async ensureToken() {
    if (this.session.isAuthenticated) {
      this.accessToken = await this.cognito.getIdToken();
    }
  }

  async ajax() {
    await this.ensureToken();
    return super.ajax(...arguments);
  }