It has been a one-week challenge for me to create a neat implementation of auth0 authentication in our Next.js project.The @auth0/nextjs-auth0 SDK was published in September 2019, and there have been multiple nice tutorials online that runs through the work from the beginning, but they all happily finish after implementing one Profile page and do not offer further suggestions on good practices when multiple pages need the user authentication information.
After many trial-and-errors, I am now (hopefully) settled with an implementation utilising api endpoints and _app.js.
Here’s a review of what I have done.


Auth0 Registration

I first went to auth0.com and signed up for an account, which comes with an auth0 domain.
I then created a Regular Web App.
The fields that are required in the Settings are:
Allowed Callback URLs, Allowed Web Origins, and Allowed Logout URLs.

For local development and testing, I set:

Once the application gets deployed, the deployed URLs can be added to corresponding fields, separated with other URLs by a comma(,).

Save the changes to settings, and now scroll up to note down three pieces of basic information:

  • Domain (something.auth0.com),
  • Client ID
  • Client Secret
    They will be needed for the auth0 configuration.

nextjs-auth0 Configuration

The installation and configuration of @auth0/nextjs-auth0 are fully explained on their github page. The only thing to be noted is that, while they put the clientId, clientSecret etc. straight into the /utils/auth0.js file, We should actually put them into the .env file for the security of credentials and for the flexibility among multiple deployments. I also learnt from my team mate that although the .env file is never committed, a .env.example file could be shared through git to present the required .env variables. dotenv module is installed to load environment variables from a .env file into process.env.

Creating API Routes

I copied and pasted the code from the @auth0/nextjs-auth0 github page for these api endpoints:

  • login, to handle user log in. This will redirect user to auth0.
  • callback, this gets called once user comes back from auth0. It will create a cookie that contains the user session, encrypted with the CookieSecret provided in the configuration step.
  • logout, to handle user log out. This removes the cookie.
  • me, to return the user profile to the client.

Accessing user info from client

Attempt 1: MyApp.getInitialProps

According to this tutorial, the Profile page accesses the user session in the getInitialProps function, which will set the user to Profile’s props:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Profile.getInitialProps = async ({ req, res }) => {
if (typeof window === 'undefined') {
const { user } = await auth0.getSession(req);
if (!user) {
res.writeHead(302, {
Location: '/api/login'
});
res.end();
return;
}

return { user }
}
}

I went to learn more about getInitialProps from the next.js documentation, and learnt that:

getInitialProps enables server-side rendering in a page and allows you to do initial data population, it means sending the page with the data already populated from the server.
getInitialProps is an async function that can be added to any page as a static method.
getInitialProps will disable Automatic Static Optimization, which means with the blocking data requirement, Next.js will render the page with getInitialProps on-demand, per-request (meaning Server-Side Rendering), instead of pre-rendering the page to static HTML.
getInitialProps can not be used in children components, only in the default export of every page.

Therefore, for the current project implementation, there are two issues that stop me from using getInitialProps for each of my pages:

  1. getInitialProps could only be called by a page component, and therefore components like TopBar, and page components that are exported in wrappers like withLayout(Map) could not use getInitialProps to fetch the data.
  2. There will be many pages in our website protected by authentication, and including this piece of logic in every pages will not look good (In the end I still included the same logic in each page, but not as naked as this piece).

With these two problems in mind, my first idea was to make the user property accessible to all pages through customising _app.js. I migrated the Profile.getInitialProps to MyApp.getInitialProps, and pass the user property to the Layout and Component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { Component, pageProps, user } = this.props;
return (
<>
<Head>
<title>TripTime: Time for our next Adventure</title>
<meta
name='viewport'
content='initial-scale=1.0, width=device-width'
/>
</Head>
<MainLayout user={user}>
<Component {...pageProps} user={user} />
</MainLayout>
</>

For some time I thought this was a perfect solution, until I found out that I had to refresh each page to have the current login status. It looks like when I was browsing through Next.js Links, MyApp.getInitialProps does not get invoked again until I refresh the page. So this is how the first attempt failed.

Attempt 2: React Hooks stuff

I will not go through this implementation in detail as it is largely based on this tutorial. Up to this moment I don’t know what React.useEffect, React.useContext and React.useState do. I’ll come back to learn more about that later.

Attempt 3: componentDidMount

When I was talking about the first attempt with my team mates, they suggested that if I did not pass the user object, but instead pass a getUser function to be called by components, the user information will be forced to be loaded for each component. Based on this idea, here’s the new \pages\_app.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import '../css/global.css';
import React from 'react';
import App from 'next/app';
import MainLayout from '../components/layout/MainLayout';
import Head from 'next/head';
import fetch from 'isomorphic-unfetch';

export default class MyApp extends App {
setUser() {
return async component => {
fetch('/api/me')
.then(response => (response.ok ? response.json() : null))
.then(user =>
component.setState(() => {
return { user: user };
}),
);
};
}

render() {
const { Component, pageProps } = this.props;
return (
<>
<Head>
<title>TripTime: Time for our next Adventure</title>
<meta
name='viewport'
content='initial-scale=1.0, width=device-width'
/>
</Head>
<MainLayout setUser={this.setUser()}>
<Component {...pageProps} setUser={this.setUser()} />
</MainLayout>
</>
);
}
}

As can be seen, _app.js defines a function setUser that returns an async function. This async function takes in a component, fetches the user information, and sets the user information into the component’s state. The setUser function is passed on to both MainLayout and Component as a property.

Now where do I call the setUser method?

According to react documentation, render is not the place to call setState:

The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser.

Not constructor either:

Typically, in React constructors are only used for two purposes:
Initializing local state by assigning an object to this.state.
Binding event handler methods to an instance.
You should not call setState() in the constructor(). Instead, if your component needs to use local state, assign the initial state to this.state directly in the constructor:

Down on the React Component lifecycle, now I have componentDidMount:

componentDidMount() is invoked immediately after a component is mounted (inserted into the tree). Initialization that requires DOM nodes should go here.
You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state.

So here’s my third version of implementation, using Dashboard as an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React from 'react';
import TripSummary from '../components/dashboard/TripSummary';
import trip from '../dummy-data/trip';
import TripTeamLayout from '../components/layout/TripTeamLayout';
import PropTypes from 'prop-types';

export default class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = { user: null };
}

componentDidMount() {
this.props.setUser(this);
}

render() {
return (
<TripTeamLayout user={this.state.user}>
<TripSummary trip={trip} user={this.state.user} />
</TripTeamLayout>
);
}
}

Dashboard.propTypes = {
setUser: PropTypes.func,
};

My next steps

  • Passing the function around gives me a weird happy thrill. JavaScript is a fun language and I’ll learn more about it!
  • What on hell is React Hook and what did the second version do with it remains a mystery to me at this moment. I’ll go further into that as soon as possible.
  • The login handler of auth0 seems to provide the option to store state, so that user can come back after they login, instead of being redirected to the profile page all the time. It’s an extra nice thing to do when the main features of the website are achieved.

Coding on!