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

Invalidate session after token expiration #195

Closed
wants to merge 3 commits into from

Conversation

jembezmamy
Copy link
Contributor

@jembezmamy jembezmamy commented Mar 31, 2017

Here's mine attempt to resolve #174. I've added handleAccessTokenExpiration method that invalidates session as soon as token expires. I had to change some existing tests since they were just checking if Ember.run.later was called and now I use it also for scheduling expiration.

@jembezmamy jembezmamy changed the title Invalidate session after token expiration - resolves #174 Invalidate session after token expiration Mar 31, 2017
@fenichelar
Copy link
Owner

Been using this in production for a while now without any issues. Would love to get this merged..... @jpadilla

@fenichelar
Copy link
Owner

@jpadilla Thoughts?

@fenichelar
Copy link
Owner

@jpadilla Any word on this?

@jpadilla
Copy link
Contributor

@fenichelar I obviously need help maintaining this addon, in case you're interested. I started updating to latest ember-cli (#222) to hopefully see what's up with CI. Any help with that and updating this PR is more than welcome.

@fenichelar
Copy link
Owner

fenichelar commented Apr 29, 2018

@jpadilla We use this library on a number of projects, it has been extremely helpful, thank you!!

I just upgraded one of our applications to the latest version of Ember. I am still in the progress of getting everything working.

I am testing #219 on that application. ETA - early this week.

We have been using this pull request (#195) in production for a long time, it has been working well.

After I get the application all squared away, I'll be able to report back on #195, #219, and #222 and make any modifications as needed.

I can then assist with any other pull requests or issues.

@jpadilla
Copy link
Contributor

jpadilla commented May 1, 2018

@fenichelar that would be amazing, thank you! ✨

@fenichelar
Copy link
Owner

I have got one of my applications updated to Ember CLI v3.1.2.

I am using the changes defined on this PR and the changes defined on PR #219 by overriding jwt.js and my adapter.

So far everything looks good. Beta users are testing now, I will report back any issues we find.

This is my app/authenticators/jwt.js:

import EmberObject from '@ember/object';
import { get } from '@ember/object';
import { Promise, resolve } from 'rsvp';
import { isEmpty } from '@ember/utils';
import { assign } from '@ember/polyfills';
import { cancel, later } from '@ember/runloop';
import JWT from 'ember-simple-auth-token/authenticators/jwt';

export default JWT.extend({
  getAuthenticateData(credentials) {
    var authentication = {
      [this.identificationField]: credentials.identification,
      [this.passwordField]: credentials.password,
      otp: credentials.otp
    };
    return authentication;
  },
  handleAuthResponse(response) {
    const token = get(response, this.tokenPropertyName);

    if (isEmpty(token)) {
      throw new Error('Token is empty. Please check your backend response.');
    }

    const tokenData = this.getTokenData(token);
    const expiresAt = get(tokenData, this.tokenExpireName);
    const tokenExpireData = {};

    tokenExpireData[this.tokenExpireName] = expiresAt;
    this.scheduleAccessTokenExpiration(expiresAt);

    if (this.refreshAccessTokens) {
      const refreshToken = get(response, this.refreshTokenPropertyName);

      if (isEmpty(refreshToken)) {
        throw new Error('Refresh token is empty. Please check your backend response.');
      }

      this.scheduleAccessTokenRefresh(expiresAt, refreshToken);
    }

    return assign(this.getResponseData(response), tokenExpireData);
  },
  restore(data) {
    const dataObject = EmberObject.create(data);

    return new Promise((resolve, reject) => {
      const now = this.getCurrentTime();
      const token = dataObject.get(this.tokenPropertyName);
      const refreshToken = dataObject.get(this.refreshTokenPropertyName);
      let expiresAt = dataObject.get(this.tokenExpireName);

      if (isEmpty(token)) {
        return reject(new Error('empty token'));
      }

      if (isEmpty(expiresAt)) {
        const tokenData = this.getTokenData(token);

        expiresAt = tokenData[this.tokenExpireName];
        if (isEmpty(expiresAt)) {
          return resolve(data);
        }
      }

      if (expiresAt > now) {
        const wait = (expiresAt - now - this.refreshLeeway) * 1000;

        this.scheduleAccessTokenExpiration(expiresAt);

        if (wait > 0) {
          if (this.refreshAccessTokens) {
            this.scheduleAccessTokenRefresh(dataObject.get(this.tokenExpireName), refreshToken);
          }
          resolve(data);
        } else if (this.refreshAccessTokens) {
          resolve(this.refreshAccessToken(refreshToken));
        } else {
          reject(new Error('unable to refresh token'));
        }
      } else {
        if (this.refreshAccessTokens) {
          resolve(this.refreshAccessToken(refreshToken));
        } else {
          reject(new Error('token is expired'));
        }
      }
    });
  },
  invalidate() {
    cancel(this._refreshTokenTimeout);
    delete this._refreshTokenTimeout;
    cancel(this._tokenExpirationTimeout);
    delete this._tokenExpirationTimeout;
    return new resolve();
  },
  scheduleAccessTokenExpiration(expiresAt) {
    const now = this.getCurrentTime();
    const wait = Math.max((expiresAt - now) * 1000, 0);
    if (!isEmpty(expiresAt)) {
      cancel(this._tokenExpirationTimeout);
      delete this._tokenExpirationTimeout;
      this._tokenExpirationTimeout = later(this, this.handleAccessTokenExpiration, wait);
    }
  },
  handleAccessTokenExpiration() {
    return this.invalidate().then(() => {
      this.trigger('sessionDataInvalidated');
    });
  }
});

This is my app/adapters/application.js:

import { get } from '@ember/object';
import { inject } from '@ember/service';
import { isEmpty } from '@ember/utils';
import DS from 'ember-data';
import ENV from '../config/environment';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';

export default DS.RESTAdapter.extend(DataAdapterMixin, {
  session: inject('session'),

  host: ENV.api,
  coalesceFindRequests: true,

  authorize(xhr) {
    const data = get(this, 'session.data.authenticated');
    const token = get(data, 'token');

    if (this.get('session.isAuthenticated') && !isEmpty(token)) {
      xhr.setRequestHeader('Authorization', `Bearer ${token}`);
    }
  },

  updateRecord(store, type, snapshot) {
    let data = {};
    let serializer = store.serializerFor(type.modelName);

    serializer.serializeIntoHash(data, type, snapshot);

    let id = snapshot.id;
    let url = this.buildURL(type.modelName, id, snapshot, 'updateRecord');

    return this.ajax(url, 'PATCH', { data: data });
  }
});

@fenichelar
Copy link
Owner

@jpadilla All seems well. I will fork, make the following changes, and submit a PR:

  1. Add the functionality from Invalidate session after token expiration
  2. Add the functionality from Add TokenAuthorizerMixin
  3. Upgrade Ember CLI to 3.1.2 as described in Update ember-cli to 3.1.2
  4. Fix Travis

Please let me know if you don't like the idea of a single, new PR. Expect the PR sometime this week.

@fenichelar
Copy link
Owner

Getting all the unit tests and demo working is taking sometime. I was having some issues with the app not getting cleaned up between tests causing configuration changes to leak to other tests. I ended up changing a lot of the configuration stuff to get it working, it is much simpler now. Just need to get the demo working and do some actual testing.

@fenichelar
Copy link
Owner

@jpadilla See #225

@fenichelar
Copy link
Owner

@jembezmamy These changes are awesome! Your commits have been pulled in through a different PR: #226

@fenichelar fenichelar closed this May 24, 2018
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

Successfully merging this pull request may close these issues.

Session isAuthenticated true for expired tokens
3 participants