auth0 / auth0.js

Auth0 headless browser sdk
MIT License
1k stars 493 forks source link

idToken coming back as undefined after upgrade #349

Closed patrickml closed 7 years ago

patrickml commented 7 years ago

After upgrading from 7.5 to 8.2 we are getting an error when trying to login. When inspecing the .on authenticated function there doesn't seem to be a idToken or anything in the param. When logging into auth0's website it is showing that the login was successful, but it was not.

Code:

/*
 * Taken from
 * https://github.com/auth0-samples/auth0-react-sample.git
 *
 */
import { EventEmitter } from 'events';
import jwtDecode from 'jwt-decode';
import Auth0Lock from 'auth0-lock';
import apolloClient from 'apollo';
import store from 'store';
import gql from 'graphql-tag';
import { browserHistory } from 'react-router';

import {
  SHOW_LOGIN_MODAL,
  HIDE_LOGIN_MODAL,
  REQUEST_PROFILE,
  RECEIVE_PROFILE,
  LOGOUT,
} from './constants';

import { setUserId } from 'containers/App/actions';

const updateProfileGQL = gql`
  mutation upsertUser($user: UserInput!) {
    upsertUser(user: $user)
  }
`;

const userMapping = [
  { auth0: 'email', gql: 'email' },
  { auth0: 'email_verified', gql: 'emailVerified' },
  { auth0: 'picture', gql: 'picture' },
  { auth0: 'given_name', gql: 'firstName' },
  { auth0: 'family_name', gql: 'lastName' },
  { auth0: 'gender', gql: 'gender' },
];

const auth0GQLMapUser = (a0user) => {
  const gqlUser = {};
  userMapping.forEach((f) => {
    gqlUser[f.gql] = a0user[f.auth0];
  });
  return gqlUser;
};

const HOUR = (60 * 60 * 1000);

const lock = new Auth0Lock(process.env.AUTH0_CLIENT_ID, process.env.AUTH0_DOMAIN, {
  closable: true,
  languageDictionary: {
    title: 'uTu Console',
  },
  loginAfterSignUp: true,
  theme: {
    logo: 'https://s3.amazonaws.com/utu-console-images/logo.png',
  },
  auth: {
    redirectUrl: `${window.location.origin}/loginRedirect`,
    responseType: 'token',
  },
  scope: 'openid offline_access',
});

class AuthService extends EventEmitter {
  constructor() {
    super();

    // binds login functions to keep this context
    this.login = this.login.bind(this);

    lock.on('authenticated', (authResult) => {
      console.log(authResult);
      this.setToken(authResult.idToken);
      this.setUserId(authResult.idToken);
      store.dispatch(fetchProfile(authResult.idToken));
      browserHistory.push(authResult.state);
      this.setLogoutTimer();
    });

    // Add callback for lock `authorization_error` event
    lock.on('authorization_error', (err) => {
      // eslint-disable-next-line no-console
      console.error('AUTH0 auth error', err);
    });

    if (this.getToken()) {
      // jwt has time in seconds
      if (this.tokenTimeToExpiry() - HOUR < 0) {
        this.logout();
        return;
      }

      this.setLogoutTimer();

      /*
       * Not adding token to redux is a concious decision
       * as we'll likely include redux store in bug requests and potentially
       * logs as well, and we'd prefer to have the credentials not saved
       * in such mediums.
       */
      this.setUserId(this.getToken());
      store.dispatch(fetchProfile(this.getToken()));
    }
  }

  /*
   * Returns in milliseconds if valid jwt provided
   */
  tokenTimeToExpiry() {
    return this.getToken() &&
      (jwtDecode(this.getToken()).exp * 1000) - new Date();
  }

  /*
   * Handle expiry of token while in the middle of a session
   * as graceful as possible.  Logout slightly before the expiry of the
   * token to prompt again for login, rather than continiuing to send
   * bad api requests.
   *
   * 1 hour before choosen fairly arbitrarily, and is intentded to account
   * for differences in our clock and the server clocks (unlikely to be
   * nearly this far off though).
   *
   * Auth0 recommends not using
   * refresh tokens for web applications, so we're sticking with a fairly
   * high expiry jwt.
   *
   * Default expiry is around 7 days as of now.
   */
  setLogoutTimer() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = undefined;
    }

    if (this.getToken()) {
      this.timeout = setTimeout(() => {
        this.logout();
      }, (this.tokenTimeToExpiry() - HOUR));
    }
  }

  /*
   * Update redux with userId
   */
  setUserId(token) {
    const userId = jwtDecode(token).sub;
    store.dispatch(setUserId(userId));
  }

  getToken() {
    // Saves user token to localStorage
    return localStorage.getItem('id_token');
  }

  setToken(idToken) {
    // Saves user token to localStorage
    localStorage.setItem('id_token', idToken);
  }

  login({ closable = true, redirectUrl }) {
    if (this.getToken()) {
      if (redirectUrl) {
        browserHistory.push(redirectUrl);
      }
      return;
    }

    lock.show({
      closable,
      auth: {
        params: {
          state: redirectUrl || window.location.pathname,
        },
      },
    });
  }

  hide() {
    lock.hide();
  }

  logout() {
    localStorage.removeItem('id_token');
    store.dispatch(setUserId(null));
    clearTimeout(this.timeout);
  }
}

let authService;

/*
 * Actual actions
 */
export const showLoginModal = (options) => {
  authService.login(options);

  return { type: SHOW_LOGIN_MODAL, options };
};

export const hideLoginModal = () => {
  authService.hide();

  return { type: HIDE_LOGIN_MODAL };
};

const requestProfile = () => ({
  type: REQUEST_PROFILE,
});

const receiveProfile = (profile) => ({
  type: RECEIVE_PROFILE,
  profile,
});

export const logout = () => {
  authService.logout();

  return { type: LOGOUT };
};

export const fetchProfile = (token) => (dispatch) => {
  dispatch(requestProfile());

  return new Promise((res, rej) => {
    lock.getProfile(token, (error, profile) => {
      if (error) {
        rej(error);
      } else {
        const user = auth0GQLMapUser(profile);
        user.id = jwtDecode(token).sub;
        apolloClient.mutate({
          mutation: updateProfileGQL,
          variables: { user },
        });

        dispatch(receiveProfile(profile));
        res();
      }
    });
  });
};

authService = new AuthService(process.env.AUTH0_CLIENT_ID, process.env.AUTH0_DOMAIN);

Screenshots: image

Auth0 Lock: Upgraded from 7.5 to 8.2 Browser: Version 55.0.2883.95 (64-bit) on OSX 10.12.2

hzalaz commented 7 years ago

@patrickml I see you are not using auth0.js but Lock. Did you change Lock version instead?.

Also put the scope inside the auth.params

patrickml commented 7 years ago

Ah i guess so, it looks like there was a change in the response_type with the version changes.

by changing responseType: 'token', to responseType: 'id_token token', it seemed to fix it.

I think these docs are outdated. https://auth0.com/docs/quickstart/spa/react/01-login

I found the fix via this source. https://github.com/auth0-samples/auth0-react-sample/blob/master/11-OAuth2-Authorization/src/utils/AuthService.js#L20

hzalaz commented 7 years ago

@patrickml it should work in Lock in the latest version without having to specify id_token as the response_type.

patrickml commented 7 years ago

@hzalaz odd, i definitely needed to add it for it to come back in the response

hzalaz commented 7 years ago

@patrickml can you open an issue in lock repo with the snippet, and the version you are using?

patrickml commented 7 years ago

@hzalaz done thank you.