Using React with JWT – Visionmate – A full-service digital partner

Using React with JWT

Over the last few years there has been a huge change in how we use software. The focus has shifted from desktop to lightweight cloud-based application. Companies like Facebook and Google have been at the forefront of advancing these technologies.

Google has introduced Angular, while Facebook is responsible for React. At Visionmate we have chosen the latter to build our applictions. In this article, I’m describing how we approach security.

JSON Web Token (JWT) is a popular way to secure your application. It is described in detail in RFC 7519 standard.

The main advantage of using a JWT token is that you can inject extra information, which can be only decoded by your back-end code.

At Visionmate we employ Grails as a back-end framework and use Spring Security REST plugin to generate and handle JWT tokens. This is not covered in this article, but we are planning to write another one about it.

In this example, I’m using Redux to handle application state, which includes authentication data and token information.

The login function returns not only the token itself, but also assigned user roles. This tells the application, what kind of rights the user has – for example whether they can edit certain documents or have access to the user management panel.

The API login function (all calls are made using axios):

function login(user) {
        const config = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return axios.post(`${SERVER_URL}/api/login`, user, config)       
    }

The login action:

export function login(username, password) {
    const user = {
        username,
        password
    };
    return (dispatch) => {
        ApiConnector.login(user)
            .then(response => {
                const data = response.data
                const user = {
                    access_token: data.access_token,
                    refresh_token: data.refresh_token,
                    roles: data.roles,
                    username: data.username,
                };
                sessionStorage.setItem('user', JSON.stringify(user));
                dispatch({
                    type: LOGIN,
                    user
                });
            })
            .catch((error) => {
                console.log(error)
            })
    }
}

Since we are using an asynchronous request, redux-thunk middleware is used to handle it. As you can see, the response includes not only access and refresh tokens, but also user roles, which can be later used to for example conditionally render additional panels on the dashboard.

The token is then stored in either session storage or local storage – depending on the application requirements, so the user doesn’t have to login each time a browser is refreshed.

 The authentication data is then stored in a reducer:

export default function (state = {loggedIn: false}, action) {
    switch(action.type) {        
        case LOGIN:
            return {...state,
                user: action.user,                 
                loggedIn: true                
            };
        case LOGOUT:
            return {...state,
                user: {},
                loggedIn: false
            };
        default:
            return state
    }
}

JWT token has to be included in every call. Therefore, it can be stored in a separate JavaScript file to be retrieved easily later on.

In this case, it’s stored in headers.js:

export default () => {
    return {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${sessionStorage.getItem('user') ? JSON.parse(sessionStorage.getItem('user')).access_token : null}`
    }
}

The headers() function is then added to every API call as shown below.

import headers from "../security/headers";

function get(target, params) {
       const config = {
           headers: headers(),
           params
       }
       return axios.get(`${SERVER_URL}/api/${target}`, config)
   }

When the access token expires and is invalid, the user does not need to log in again – it’s enough to use the refresh token to obtain a new valid access token.

Refresh token function:

function refreshToken() {
        const config = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        };
        const body = {
            grant_type: "refresh_token",
            refresh_token: JSON.parse(sessionStorage.user).refresh_token
        };
        axios.post(`${SERVER_URL}/oauth/access_token`, qs.stringify(body), config)
            .then(response => {
                const data = response.data
                const user = {
                    access_token: data.access_token,
                    refresh_token: data.refresh_token,
                    roles: data.roles,
                    username: data.username                    
                };
                sessionStorage.setItem('user', JSON.stringify(user));
                store.dispatch({
                    type: REFRESH_TOKEN,
                    user
                })
            })
    }

In order to log the user out, simply delete the token from session/local storage as shown in the action below.

Logout action:

export function logout() {
    sessionStorage.removeItem('user');
    return {
        type: LOGOUT
    }
}

Summary

As shown in this article, in order to use JWT with your application, certain steps need to be followed. This example can be used as a template to add more functionality and expand your application.

Libraries used: redux, redux-thunk and axios.

Feel free to ask questions and share your comments!

Origin: www.linkedin.com

All articles