score:3

you can set the access token in the local storage on login and clear it after the user logs out. the is authenticated method will then be used to check if there is a token and whether the token is valid while making an API call

score:6

If you are using an application where the authentication lasts only for one session, storing it in state is enough. But do note that this means, the user will lose the authenticated status on page refresh.

Here is an example using React Context, where we create context using createContext and use Consumer to access it across the application.

const AuthenticationContext = React.createContext();
const { Provider, Consumer } = AuthenticationContext;

function Login(props) {
  return (
    <Consumer>
      {
        value=>
        <button onClick={value.login}>Login</button>
      }
    </Consumer>
  );
}

function Logout() {
  return (
    <Consumer>
      {
        value=>
        <button onClick={value.logout}>Logout</button>
      }
    </Consumer>
  );
}

function AnotherComponent() {
  return (
    <Consumer>
      {
        value=>{
          return value.isAuthenticated?
            <p>Logged in</p>:
            <p>Not Logged in</p>
        }
      }
    </Consumer>
  );
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.login = ()=> {
      this.setState({
        isAuthenticated: true
      });
    }
    this.logout = ()=> {
      this.setState({
        isAuthenticated: false
      });
    }
    this.state = {
      isAuthenticated: false,
      login: this.login,
      logout: this.logout
    }
  }
  
  render() {
    return (
      <Provider value={this.state}>
        <Login />
        <Logout />
        <AnotherComponent />
      </Provider>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div> 

https://reactjs.org/docs/context.html#reactcreatecontext

score:121

Handling isAuthenticated only in the state means the user will be unauthenticated every time he refreshes the page. That's not really user-friendly! :)

So instead, the Login page should store an access_token (coming from your backend) in the cookies or localStorage of the browser. An access_token proves the user is authenticated and also verifies his identity. You will usually pass this access_token to every next requests to your server, to check if this user is allowed to access the data he's requesting, or allowed to create, edit and delete the things he's trying to create, edit and delete.

Then you can check this access_token on every other pages as well and redirect the user to the Login page if he's not authenticated anymore.


A brief aside on the difference between access_token and refresh_tokenthis will help you understand the code bellow, but feel free to skip ahead if you are already familiar with it.

Your backend probably uses OAuth2, which is the most common authentication protocol nowadays. With OAuth2, your app makes a first request to the server containing the username and password of the user to authenticate. Once the user is authenticated, he receives 1) an access_token, which usually expires after an hour, and 2) a refresh_token, which expires after a very long time (hours, days). When the access_token expires, instead of asking the user for his username and password again, your app sends the refresh_token to the server to obtain a new access_token for this user.


A brief aside on the differences between cookies and localStoragefeel free to skip it too!

localStorage is the most recent technology between both. It's a simple key/value persistence system, which seems perfect to store the access_token and its value. But we also need to persist its date of expiration. We could store a second key/value pair named expires but it would be more logic to handle on our side.

On the other hand, cookies have a native expires property, which is exactly what we need! cookies are an old technology and are not very developer-friendly, so I personally use js-cookie, which is a small library to manipulate cookies. It makes it look like a simple key/value persistence system too: Cookies.set('access_token', value) then Cookies.get('access_token').

Other pro for the cookies: they are cross subdomains! If your Login app is login.mycompany.com and your Main app is app.mycompany.com, then you can create a cookie on the Login app and access it from the Main app. This is not possible with LocalStorage.


Here are some of the methods and special React components I use for authentication:

isAuthenticated()

import Cookies from 'js-cookie'

export const getAccessToken = () => Cookies.get('access_token')
export const getRefreshToken = () => Cookies.get('refresh_token')
export const isAuthenticated = () => !!getAccessToken()

authenticate()

export const authenticate = async () => {
  if (getRefreshToken()) {
    try {
      const tokens = await refreshTokens() // call an API, returns tokens

      const expires = (tokens.expires_in || 60 * 60) * 1000
      const inOneHour = new Date(new Date().getTime() + expires)

      // you will have the exact same setters in your Login page/app too
      Cookies.set('access_token', tokens.access_token, { expires: inOneHour })
      Cookies.set('refresh_token', tokens.refresh_token)

      return true
    } catch (error) {
      redirectToLogin()
      return false
    }
  }

  redirectToLogin()
  return false
}

redirectToLogin()

const redirectToLogin = () => {
  window.location.replace(
    `${getConfig().LOGIN_URL}?next=${window.location.href}`
  )
  // or history.push('/login') if your Login page is inside the same app
}

AuthenticatedRoute

export const AuthenticatedRoute = ({
  component: Component,
  exact,
  path,
}) => (
  <Route
    exact={exact}
    path={path}
    render={props =>
      isAuthenticated() ? (
        <Component {...props} />
      ) : (
        <AuthenticateBeforeRender render={() => <Component {...props} />} />
      )
    }
  />
)

AuthenticateBeforeRender

class AuthenticateBeforeRender extends Component {
  state = {
    isAuthenticated: false,
  }

  componentDidMount() {
    authenticate().then(isAuthenticated => {
      this.setState({ isAuthenticated })
    })
  }

  render() {
    return this.state.isAuthenticated ? this.props.render() : null
  }
}

Related Query

More Query from same tag