JSON Web Token authentication with Preact and Redux - Part 1
February 01, 2018
This is part 1 of a 3 part post about a recent project I’ve made for Preact with JWT authentication using Apollo GraphQL boilerplate. Instead of patching together different concepts and technologies on a per project basis, I’ve made this helpful starter pack which is great for testing and prototyping JWT based auth with Apollo.
Full code can be found in a link at the bottom of this post.
- Part 1: Preact using Preact CLI, with Node based JWT authentication - [you are here]
- Part 2: Integrating Apollo’s GraphQL for server communication - view here
- Part 3: Integrating Apollo’s GraphQL for client communication and data management - view here
Post overview
This first part of the 3 part post will discuss building the application using Redux for client side state management, without GraphQL/Apollo at all. One of GraphQL/Apollo’s main advantage is that it can be introduced to an existing project, so I want to help readers understand how a project without any GraphQL integration can be built, then in the second post, altered to include the GraphQL layer, as well as in the third post, discuss how Redux fits in with Apollo considering [Redux was dropped] (https://www.apollographql.com/docs/react/2.0-migration.html#redux) from Apollo 2.0 in December 2017.
This post assumes some knowledge of the technologies mentioned, but I’ve tried to put some links out to good resources if you’re coming from a beginners level.
Introduction
One of the most common tasks on a web application is authentication. It’s one of those where, especially in the Node ecosystem, there are hundreds of different ways to develop. So many tutorials go about things in different ways - making use of popular libraries like Passport or writing your own solutions, each route having it’s merits. One of the most important things about an authentication system though is security, and this was addressed in a recent popular Hackernook post which went through some top Google ranked Node authentication tutorials and highlighted some issues (most of which have now been fixed by the way).
It’s worth noting here that the JWT auth token and refresh token is stored on the browser in localStorage. This should not be used in production level applications and I discuss this more later in the post.
For part 1 of this 2 part post, I’ll run through how I integrated a custom JWT (JSON web token) authentication process into a basic web app made using Preact CLI. The authentication method adopts the a refresh token approach to using JWT’s, giving administrators the ability to revoke access to a user amongst other things - something which is not such an easy decision to make when using a single JWT access token. For a quick run down of some thoughts on JWT’s and refresh tokens see this Stack Overflow question, and this Hacker News post.
I won’t go into a huge amount of detail on JSON Web Tokens and how I integrated into the server side because I did a popular blog post on this last year concentrating on JWT auth with React Native which goes into a good amount of detail on the server side (and uses the same server-side codebase).
Technologies used
Preact CLI
If you haven’t come across already, Preact is a “fast 3kB alternative to React with the same modern API”. It’s excellent for starter projects, prototyping, and also great for production ready systems wanting to take advantage of small file sizes, built in bundling and a number of other performance enhancements.
There are a few differences, most minor, and most very helpful which can be found in a great list on the Preact site. A couple notable bits I come across when making this boilerplate were:
- You can’t eject or see the Webpack file like you can with Create React App. This is not too much of a problem as things like CSS Modules are already integrated, but if you do want to edit Webpack config, you must overwrite.
- Most plugins built for React will not work with React straight out of the box (such as Redux). There have been alternative Preact wrappers of some major plugins like Redux, but for those where there are no wrappers, there’s an option to use a plugin called Preact Compat to bridge the gap and use all React plugins.
JSON Web Tokens (JWT’s)
The JWT process flows over both front and back end. As mentioned above, this post will focus on the front-end of the project in terms of JWT’s as the server side setup is the same as my pervious React Native post.
JSON web tokens are a method of stateless authentication used to replace traditional session based authentication. A JWT is an encoded string which can be verified with a “secret key” for authentication. It can hold a payload of information which tends to be user related info (name, profile pic for example) and used on a website or app. You can find out more info and a chance to generate and decode a JWT from the official website.
Many tutorials or posts covering JSON Web Tokens do not cover an approach using refresh tokens which are extremely useful for managing authentication a little better. For example, you can set a JWT expiry time to anything you like (5 minutes, 5 days, 5 years!) and once a user has that token, they will have access to your system. As it’s stateless authentication (there’s no check against a server-side session) there’s not an easy way to revoke access to a particular user. With refresh tokens kept in the users database entry for reference, you can set a JWT token to last for, say 1 hour, and then use the refresh token associated with that user to generate a new auth token when the auth token has expired.
Project setup
Firstly, I set up Preact CLI as per the docs. If you’ve used React before, this is pretty much exactly the same as Create React App. There are a few differences between Preact and React which are also covered in the docs, but for the most part you may not notice them.
You may notice the Preact CLI starter project is very simple and gives a pretty good structure of components to run with initially, having routes and components split out into different folders. I’ve ran with the default project structure in this boilerplate, adding other directories which are /api
for remote request handling using fetch()
and /reducers
for Redux implementation.
To Redux or not to Redux
State management is a vital part of any application so I want to cover state management and discuss the decision I ended up with. As this boilerplate is based on using Apollo and GraphQL for state management (and Apollo was recently built on top of Redux until V2), you may be left thinking why use Redux at all? This Github question will help answer that question. Basically, it’s very likely that your app needs a central store to manage state which has nothing to do with talking to the back end of your application. One such example is using JWT’s, and more specifically allowing the whole application to read from and update the JWT’s.
In both my previous JWT and React Native post and this one, Redux is the main feature used in refreshing the JWT’s as I have used Redux Middleware to intercept dispatch()
functions in order to check JWT validity (I’ll cover this shortly). This is the cleanest way I could think of to assist with JWT authentication, as well as it keeping the auth processing almost completely separated from the rest of the code and not duplicated.
Aside from that, there may be many more situations where you’s want global state held with Redux, but I’ll cover that more in the next post which focuses on the Apollo side of things.
To set up with Redux, I installed the following packages:
npm i redux preact-redux redux-logger redux-thunk
Preact Redux is a wrapper around Redux which means it can be used in a Preact environment. This is similar to React Redux for React. Redux Logger is used to show all dispatch actions in the console which is hugely useful, and Redux Thunk is used to handle asynchronous actions in Redux.
Async Actions are a vital part of most Redux project’s I’ve done, as it allows you to dispatch actions from inside a function. This opens up so many possibilities when it comes to showing/hiding loading messages before/after network requests, and doing pretty much anything involved with asynchronous behaviour.
When a project is not too big and in it’s early stages, I tend to put all of this async behaviour together with Redux reducers in files I call services. I feel it helps when quickly switching between files, which is often needed with Redux as it has quite a large amount of boilerplate code needed to run most of it’s features. For example, I have the following files to handle authentication:
- src/reducers/auth.service.js - this is where all auth based server call functions are held. Because of Redux Thunk, these functions return another function allowing us to
dispatch()
more actions. It’s helpful to keep all these together as they may perform part of an async chain of functions. - src/reducers/auth.reducer.js - this is where all actions update the global state, a standard feature of Redux.
- src/api/auth.api.js - this is where I put all of the fetch functions. Most fetch functions tend to be repeated code (handling errors, turn response to JSON, pass back to service).
- src/api/errors.api.js - handles and API/fetch related errors, including invalid JWT’s.
Storing the JWT’s
Both the auth token and refresh token are received from the server when the user registers and logs in:
// routes/home/index.js
...
import { saveTokens } from '../../reducers/auth.service';
handleSubmit(e) {
login(this.state)
.then(response => {
// tokens saved once login is complete successfully
this.props.saveTokens(response.authToken, response.refreshToken);
route('/profile');
})
.catch(err => {
this.setState({
loginError: err.message
});
});
e.preventDefault();
}
// this.props.saveTokens() uses the saveTokens() function with Redux at the bottom of this file
I’ve kept the login()
function and loginError state confined to the home route state as opposed to integrating with Redux. It’s a similar situation with the registration function and state also. I feel this helps not bog down the Redux store with too much unnecessary state.
There are further helper functions based around authentication (such as the saveTokens()
function above) in the reducers/auth.service.js
directory:
// reducers/auth.service.js
export const saveTokens = (authToken, refreshToken) => (dispatch) => {
localStorage.setItem("authToken", authToken)
localStorage.setItem("refreshToken", refreshToken)
dispatch(saveTokensToStore({ authToken, refreshToken }, jwtDecode(authToken)))
}
As you can see above, the tokens are stored in localStorage, as well as being dispatched to save to the Redux store. This is needed in this example for the following reasons:
- Saved to localStorage so the user can stay logged in after refreshing the browser. I’ll cover this a little more soon.
- Saved to Redux store so the other parts of the app using the tokens can easily access the values. In this boilerplate the values are shown on screen for users to view and decode if they wish.
There is a consideration for security by storing JWT’s in the browser, especially refresh tokens, as mentioned in this post from auth0. There are a number of other places around the web to look at for token storage such as here, here, here and here. It’s not advised for production level applications so make sure you read through all these.
Another notable point is that JWT Decode in the snippet above allows us to access the token’s payload data, which in this case is the logged in users full name and email address. This is a key benefit of using JWT’s as it means the app doesn’t need to get user info from another session based server request on each page load. The users’ details are sent to the auth.reducer.js
file where they are also added to the store.
Keeping user logged in using localStorage on browser refresh
When the browser is refreshed we want to keep the user logged in and this is done here by storing the JWT’s in localStorage. Once the browser is refreshed, the root component of the Preact app (in this case, “App”) runs a function via Redux using a Thunk called loadLocalUserAuth()
:
// reducers/auth.service.js
export const loadLocalUserAuth = () => (dispatch) => {
let refreshToken = localStorage.getItem("refreshToken")
apiGetNewToken(refreshToken).then((rsp) => {
if (rsp.success) {
return dispatch(saveTokens(rsp.authToken, refreshToken))
} else {
localStorage.removeItem("authToken")
localStorage.removeItem("refreshToken")
}
})
}
This uses the stored refresh token and fires a request off to the server to authenticate the user.
Protecting the API
The token is needed in each request so the server-side middleware can authorise the user by checking the auth token’s validity (this is all covered in the server-side section of my post on React Native with JWT).
In terms of this Preact boilerplate the token is sent via the Authorisation header below:
// api/auth.api.js
export const apiCheckToken = () => {
let authToken = localStorage.getItem("authToken")
return fetch(`${config.url}/api/getAll`, {
method: "POST",
headers: {
...config.configHeaders,
Authorization: "Bearer " + authToken,
},
})
.then((response) => response.json())
.then(handleErrors)
.catch((error) => {
throw error
})
}
Refreshing the token
Once the token has expired (as it will be short lived) we are required to use the stored refresh token to get a new auth token from the server. This is done using Redux middleware (another reason why Redux is invaluable in this situation).
// middleware.js
let buffer = []
export const jwt = (store) => (next) => (action) => {
buffer.push(action)
console.log(buffer)
if (action.type === "INVALID_TOKEN") {
let theStore = store.getState()
if (
theStore.auth &&
theStore.auth.authToken &&
theStore.auth.refreshToken
) {
if (!theStore.auth.pendingRefreshingToken) {
store.dispatch({
type: "REFRESHING_TOKEN",
})
store
.dispatch(refreshToken(theStore.auth.refreshToken))
.then((resp) => {
store.dispatch({
type: "TOKEN_REFRESHED",
})
let pos = buffer.map((e) => e.type).indexOf("INVALID_TOKEN") - 1
let previousRequest = buffer[pos]
if (typeof previousRequest === "function") previousRequest()
buffer = []
})
}
}
} else if (action.type === "REFRESH_EXPIRED") {
buffer = []
logoutUser()
} else {
if (buffer.length > 20) {
//remove all items but keep the last 20 which forms the buffer
buffer.splice(0, buffer.length - 20)
}
return next(action)
}
}
The key point here is the buffer array. Without that buffer, when the auth token is expired the server will refresh the token automatically, but forget about the initial request which was not fulfilled. By using Redux to add each action to a buffer, we’re able to pinpoint the action before the token became invalid, and fire it off when we receive the new auth token.
Full code can now be found on Github for my preact-jwt-apollo-boilerplate.
I have split the project into Git branches which show each stage of development from the below:
- Before the introduction of Redux to the state management and working JWT integration.
- Fully working JWT authentication but no Apollo integration.
Final thoughts and improvements
At it’s core, the idea of using JSON Web Tokens and refresh tokens together helps reduce the number of session auth hits to the database whilst giving control to the admins over revoking access if something was to potentially go wrong such as an unauthorised account access.
With SPA’s, this approach is far from ideal from a security standpoint because we’re storing the tokens in the browser. There are ways around this by upgrading the security around the application such as using silent authentication, which I’ll cover at some point soon. Bear in mind that this should not be used in production, but is great for getting familiar with the workings of a stateless authentication approach, and using refresh tokens.
Thanks for reading!
Senior Engineer at Haven