7

I use React Context API to store the information that a user is authenticated.

In development mode when I type in any URL that redirects to the 404 error page the context data is lost. When I navigate to a valid page a previously logged in user is not logged in any more.

EDIT: I just tested this with gatsby build and gatsby serve. A built gatsby site keeps the context when redirecting to 404 error page. But the context is still lost when navigating to completely different URL such as www.google.com.

Now my question is: How do I resupply the context with the login information without having the user be manually log in again?

Here is my AuthContextProvider wrapper class:

export class AuthContextProvider extends React.Component {
  constructor(props) {
    super(props);
    this.state = { user: {} };
  }
  
  // ...
  
  render() {
    return (
      <AuthContext.Provider value={{ getUser: this.getUser, setUser: this.setUser }}>
        {this.props.children}
      </AuthContext.Provider>
    );
  }
}  

I wrap my whole app with the Context Provider in a root layout:

const RootLayout = ({ children }) => {
  return (
    <AuthContextProvider>
      {children}
    </AuthContextProvider>
  );
}
EliteRaceElephant
  • 5,493
  • 4
  • 34
  • 50

3 Answers3

6

React Context is about providing some data to one or more child components without having to pass the data down through intermediary components. There's no built-in mechanism for persisting state between page loads, so you'll need to reach for another tool for that.

If you haven't already implemented your authentication layer, you'll want to look into how that will work. There are a number of strategies for maintaining that state, even just within using cookie-based storage. JWT (JSON Web Token) are a popular method that will let you store signed user and client-readable data in the cookie at the cost of requiring a bit more work to manage expiration/renewal and having a larger payload. Assuming that's the approach you took, you might do something like this:

import React from "react";
import jwt from "jsonwebtoken"; // Add jsonwebtoken via npm/yarn

function getCookieValue(a) {
  var b = document.cookie.match('(^|[^;]+)\\s*' + a + '\\s*=\\s*([^;]+)');
  return b ? b.pop() : '';
}

const AUTH_PUBLIC_KEY = "your JWT public key here"

export const AuthContext = React.createContext();

export class AuthContextProvider extends React.Component {
  state = {
    authenticated: false,
    userid: null,
  };

  componentDidMount() {
    jwt.verify(getCookieValue("session"), AUTH_PUBLIC_KEY, (err, session) => {
      if (!err && session.userid) {
        this.setState({ userid: session.userid, authenticated: true })
      }
    })
  }

  // Important: REMOVE THIS AFTER TESTING/DEV
  toggleLogin = () => {
    this.setState(state => ({
      authenticated: !state.authenticated,
      userid: 2,
    }));
  }

  render() {
    return (
      <AuthContext.Provider
        value={{
          ...this.state,
          toggleLogin: this.toggleLogin,
        }}
      >
        {this.props.children}
      </AuthContext.Provider>
    );
  }
}

This will parse the JWT token in the session cookie when the AuthContextProvider is mounted and update the state with the userid value stored in the JWT if one is present.

You will probably want to wrap the Gatsby App with this component, which you can do from gatsby-browser.js and gatsby-ssr.js files (create them in the root of your repo if you don't have them yet):

// gatsby-browser.js
import React from "react"
import AuthContextProvider from "components/AuthContextProvider"

export const wrapRootElement = ({ element }) =>
  <AuthContextProvider>{element}</AuthContextProvider>

// gatsby-ssr.js
import React from "react"
export { wrapRootElement } from "./gatsby-browser"

You will still need to handle generating the JWT token (probably from a backend that is handling authentication) and if it's not already being persisted in a cookie you can access from the browser you will need to handle creation of that cookie at the relevant point in your application lifecycle.

coreyward
  • 68,091
  • 16
  • 122
  • 142
1

I hope this helps you or others. The blog post below describes how you need to use gatsby-browser.js to wrap the root element in the provider so that it doesn't reset it on page change.

https://www.gatsbyjs.org/blog/2019-01-31-using-react-context-api-with-gatsby/

Kyle Pennell
  • 4,306
  • 3
  • 38
  • 65
0

You have 3 possibilities:

  1. web storage aka localStorage or sessionStorage (easiest, least secure)
  2. session cookies (secure, requires backend server)
  3. json web tokens (JWT) (most secure, requires backend server)

An excellent read about background infromation is this blog on dev.to.

1. web storage such as localStorage

This is considered to be the least secure option secure option. Do not save personal data such as email adresses here. Never ever save sensitive information such as credit card information and such.

This question describes how to use it:

var testObject = { 'one': 1, 'two': 2, 'three': 3 };

// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));

// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');

console.log('retrievedObject: ', JSON.parse(retrievedObject));

2. cookies or session cookies

For Express you can use express-ession. Other web servers have similar middleware. The point is to supply the user info within a cookie as described on MDN.

3. json web tokens

This is similar to cookies but uses JSON web tokens. @coreyward gave an excellent answer. You can also read more in this blog post.

EliteRaceElephant
  • 5,493
  • 4
  • 34
  • 50