How to persist Auth0 login status in browser for React SPA

非 Y 不嫁゛ 提交于 2020-01-24 10:03:12

问题


Currently when I create my routes, I check an Auth0 method - isAuthenticated() - to determine whether or not to return a protected page or redirect to login. However, this state only exists in memory and does not keep a user on their page upon browser refresh and I would like to do so.

This is a React/RR4/React Context app and my Auth0 methods are listed in Auth.js (below).

It is highly inadvisable to store login state in localStorage. And if I store my Auth0 tokens in cookies, I'm not sure how I would validate the tokens since there is no server validation set up. What is the correct condition to check that will enable secure data persistence?

ProtectedRoutes.jsx:

   <Route
      exact
      key={route.path}
      path={route.path}
      render={() => (
        // CONDITION TO CHECK
        context.auth.isAuthenticated()
          ? (
            <div>
              <route.component />
            </div>
          ) : <Redirect to="/login" />
        )}
      />

Auth.js (added for reference):

import auth0 from 'auth0-js';
import authConfig from './auth0-variables';

class Auth {
  accessToken;
  idToken;
  expiresAt;
  tokenRenewalTimeout;

  auth0 = new auth0.WebAuth({
    domain: authConfig.domain,
    clientID: authConfig.clientId,
    redirectUri: authConfig.callbackUrl,
    responseType: 'token id_token',
    scope: 'openid'
  });

  constructor() {
    this.scheduleRenewal();
    this.login = this.login.bind(this);
    this.logout = this.logout.bind(this);
    this.handleAuthentication = this.handleAuthentication.bind(this);
    this.isAuthenticated = this.isAuthenticated.bind(this);
    this.getAccessToken = this.getAccessToken.bind(this);
    this.getIdToken = this.getIdToken.bind(this);
    this.renewSession = this.renewSession.bind(this);
    this.scheduleRenewal = this.scheduleRenewal.bind(this);
  }

  login() {
    console.log('logging in!');
    this.auth0.authorize();
  }

  handleAuthentication() {
    return new Promise((resolve, reject) => {
      this.auth0.parseHash((err, authResult) => {
        if (err) return reject(err);
        console.log(authResult);
        if (!authResult || !authResult.idToken) {
          return reject(err);
        }
        this.setSession(authResult);
        resolve();
      });
    });
  }

  getAccessToken() {
    return this.accessToken;
  }

  getIdToken() {
    return this.idToken;
  }

  getExpiration() {
    return new Date(this.expiresAt);
  }

  isAuthenticated() {
    let expiresAt = this.expiresAt;
    return new Date().getTime() < expiresAt;
  }

  setSession(authResult) {
    localStorage.setItem('isLoggedIn', 'true');
    let expiresAt = (authResult.expiresIn * 1000) + new Date().getTime();
    this.accessToken = authResult.accessToken;
    this.idToken = authResult.idToken;
    this.expiresAt = expiresAt;
    this.scheduleRenewal();
  }

  renewSession() {
    this.auth0.checkSession({}, (err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        this.setSession(authResult);
      } else if (err) {
        this.logout();
        console.log(`Could not get a new token. (${err.error}: ${err.error_description})`);
      }
    });
  }

  scheduleRenewal() {
    let expiresAt = this.expiresAt;
    const timeout = expiresAt - Date.now();
    if (timeout > 0) {
      this.tokenRenewalTimeout = setTimeout(() => {
        this.renewSession();
      }, timeout);
    }
  }

  logout() {
    this.accessToken = null;
    this.idToken = null;
    this.expiresAt = 0;
    localStorage.removeItem('isLoggedIn');
    clearTimeout(this.tokenRenewalTimeout);
    console.log('logged out!');
  }
}

export default Auth;

回答1:


You can use Silent authentication to renew the tokens on browser refresh.

Specifically for your react SPA app

  • setup a state say tokenRenewed to false in your main App component
  • you already have a renewToken method in your auth.js so call that in componentDidMount method
componentDidMount() {
   this.auth.renewToken(() => {
      this.setState({tokenRenewed : true});
   })
}
  • update renewToken to accept a callback cb like below
renewSession(cb) {
    this.auth0.checkSession({}, (err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        this.setSession(authResult);
      } else if (err) {
        this.logout();
        console.log(`Could not get a new token. (${err.error}: ${err.error_description})`);
      }
      if(cb) cb(err, authResult);
    });
  }
  • Make sure you don't load the App component unless tokenRenewed is true i.e. unless you have the valid tokens renewed via silent authentication
render() {
    if(!this.state.tokenRenewed) return "loading...";
    return (
      // Your App component
    );
}

Notes:

  1. You may want to make sure you have the correct Allowed Web Origins set in the Application settings for this to work
  2. Silent authentication has some limitations as in it requires 3rd party cookies enabled on the browser and in case ITP for Safari. You should setup a custom domain to avoid that. Refer to the official auth0 docs to learn more here.
  3. More details on how to store token securely here


来源:https://stackoverflow.com/questions/54932922/how-to-persist-auth0-login-status-in-browser-for-react-spa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!