Authentication on Universal React with Next.js

Since Next.js gained the reputation of being the best out-of-the-box solution for Universal React, I wanted to give it a shot. The first thing I had to figure out was how to handle authentication because my current solution based on redux-auth-wrapper only worked on solutions without server side rendering. Thus, I went through the many examples available at the Next.js repo and then put together a sample app that you can check here and find the code here.

This app just lists users, show user detail pages, signs in, sings out and registers.

JWT and cookies

To access the API after authentication a Json Web Token (JWT) needs to be included in the header. However, since this is a universal app, the server also needs to have access to the JWT. Therefore, localStorage and sessionStorage are not good alternatives unlike cookies that can be accessed by both the server and the browser. Consequently, this is the alternative that I used for this solution.

Core auth and how it is used

The following files provide the basic handling of authentication and also show how they can be used.

/lib/auth.js
/lib/redirect.js
/lib/session.js
/pages/auth/login.js
/pages/auth/logout.js
/pages/auth/register.js
/pages/user.js

/lib/auth.js

All the functions to handle auth are exposed here:

/lib/redirect.js

import Router from “next/router”; 
export default (target, ctx = {}) => { 
if (ctx.res) {
// If on the server, an HTTP 303 response with a "Location"
// is used to redirect.
ctx.res.writeHead(303, { Location: target });
ctx.res.end();
} else {
// On the browser, next/router is used to "replace" the current
// location for the new one, removing it from history.
Router.replace(target);
}
};

/lib/session.js

...
// We set cookies always from the browser. They expire after a day.
// It's important to specify the path to avoid unexpected behavior.
export const setCookie = (key, value) => {
if (process.browser) {
cookie.set(key, value, { expires: 1, path: “/” });
}
};
...
// We retrieve cookies from the browser or the server.
export const getCookie = (key, req) => {
return process.browser ? getCookieFromBrowser(key) :
getCookieFromServer(key, req);
};
...

/pages/auth/login.js & /pages/auth/register.js

...
static getInitialProps(ctx) {
  // If it exists already a session started, redirect.
redirectIfAuthenticated(ctx);
  // Then they both return their initial props
...
}

/pages/auth/logout.js

...
// It's not necessary to sign out from a component, but
// it integrates smoothly that way with the Header component.
export default class Logout extends Component {
componentDidMount() {
// Session is ended
signOut();
return {};
} render() {
return null;
}
}

/pages/user.js

...
static async getInitialProps(ctx) { 
// If it does not exist session, it gets redirected
if (redirectIfNotAuthenticated(ctx)) {
return {};
}

const id = ctx.query && ctx.query.id;
const jwt = getJwt(ctx);
const res = await (id ? getUser(jwt, id) : getCurrentUser(jwt));
  return { 
user: res.data,
authenticated: !!jwt,
query: !!id
};
}
...

Run the app

The source code can be found on my GitHub repo. If you already have Node.js and Git installed, just do the following:

git clone https://github.com/carlos-peru/next-with-api.git
cd next-with-api
yarn install # or npm install
yarn run dev # or npm run dev

Thank you

Thank you for make it to the end and hope this was helpful. Please leave a comment if you want to throw a question, share an observation or just give feedback.