Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to persist Auth0 login status in browser for React SPA

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;
like image 254
evaline Avatar asked Feb 28 '19 19:02

evaline


People also ask

How do you integrate auth0 With react?

One way to integrate Auth0 with your React app is to wrap your root component with an Auth0Provider that you can import from the SDK. import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import { Auth0Provider } from "@auth0/auth0-react"; ReactDOM.

How do you handle user authentication in react?

There are two main things your React application needs to do to sign on a user: Get an access token from an authentication server. Send the access token to your backend server with each subsequent request.


1 Answers

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
like image 105
kachhalimbu Avatar answered Oct 09 '22 10:10

kachhalimbu