Spencer FengSpencer Feng

Authentication in React Apps with JWT: Part 10

Welcome back! This tutorial will be the last one in this series. In this tutorial, we will retrieve and display member news on the member news page. This tutorial will be broken down into following parts:

  • Create the route which handles the request for member only news
  • Create the memberNews reducer so that we can store member news as a state in the Redux store
  • Create the actions which respond to the member news request event
  • Update the MemberNewPage component so that it can trigger the event of making the request for member news

Create the route which handles the request for member only news

Let’s open the ‘app.js’ file and add the code below to it:

// get member news
app.post('/member-news', function(req, res, nex) {

    res.setHeader("Access-Control-Allow-Origin", "*");

    const token = req.body.token;

    jwt.verify(token, process.env.JWT_KEY, function(err, payload) {

        if (err) {
            return res.status(403).json({
                success: false,
                message: err.message
            });
        }

        const userId = payload.sub;

        User.findOne({
            '_id': userId
        }, function(err, user) {
            if (err) {
                return res.status(400).json({
                    success: false,
                    message: err.message
                });
            }

            if (user) {
                return res.json({
                    success: true,
                    message: 'The user has access to member news',
                    data: {
                        news: 
                            [
                                {
                                    title: 'member news 1 from the server',
                                    body: 'body of member news 1 from the server',
                                },
                                {
                                    title: 'member news 2 from the server',
                                    body: 'body of member news 2 from the server',
                                },
                                {
                                    title: 'member news 3 from the server',
                                    body: 'body of member news 3 from the server',
                                },
                                {
                                    title: 'member news 4 from the server',
                                    body: 'body of member news 4 from the server',
                                },
                                {
                                    title: 'member news 5 from the server',
                                    body: 'body of member news 5 from the server',
                                }
                            ]
                    }
                });
            } else {
                return res.status(401).json({
                    success: false,
                    message: 'The user does not have access to member news'
                });
            }
        });

    });

});

In the above code, we created a route ‘/member-news’ which handles the post request from the client side. The route retrieves the JWT token from the request body and then it verifies the token. If the token is valid, it then tries to find the corresponding user in the database. If the user exists, it returns the JSON object which contains member news.

Create the memberNews reducer so that we can store member news as a state in the Redux store

Member news returned from the server will be used in the member news page and it may also be used in other places in the future. So, it’s better to save it to our application state.  Let’s create a reducer for in the ‘client/src/reducers.js’ file:

export const memberNews = (state, action) => {

    switch (action.type) {
        case 'SET_MEMBER_NEWS': 
            return action.data;
        default: 
            return state || [];
    }

}

Create the actions which respond to the member news request event

Let’s open the ‘client/src/actions.js’ file and put the code below to it:

export const getMemberNews = (token, history) => {

    return dispatch => {
        fetch('http://localhost:3000/member-news', {
            method: 'post',
            mode: 'cors',
            headers: {
                "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"  
            },
            body: `token=${token}`
        })
        .then(response => {
            if (response.status >= 200 && response.status < 300) {
                return Promise.resolve(response);
            } else {
                return Promise.reject(new Error(response.statusText));
            }
        })
        .then(response => {
            return response.json();
        })
        .then(data => {
            if (data.success === true) {
                // Update the store state
                dispatch(setMemberNews(data.data.news));
            } else {
                 // Redirect to home page
                 history.push('/');
            }
        })
        .catch(err => {
            console.log(err);
        });
    }

};

export const setMemberNews = (news) => ({ type: 'SET_MEMBER_NEWS', data: news });

As you can see, in the above code, we created 2 actions: getMemberNews and setMemberNews. getmemberNews is an async action which makes a post request to the route we created earlier in this tutorial. Once we get the member news data from the server, we dispatch the setMemberNews action which passes the member news data to the memberNews reducer we created earlier in this tutorial.

Update the MemberNewsPage component so that it can trigger the event of making the request for member news

The MemberNewsPage component is responsible for following things:

  • Redirect users who are not logged in to the home page
  • Display member news for logged-in users

So, it needs to have the access to following data:

  • The current user in the application state
  • The member news in the application state

and it needs to do following things:

  • Dispatch the getMemberNews action

Based on our analysis, we need to create a container component and a presentational component for the member news page.

Let’s create the file ‘client/src/components/MemberNewsPageContainer.js’ and put the code below to it:

import React from 'react';
import { connect } from 'react-redux';
import { getMemberNews } from '../actions';
import MemberNewsPage from './MemberNewsPage';

const mapStateToProps = (state, ownProps) => ({
    cUser: state.cUser,
    memberNews: state.memberNews
});

const mapDispatchToProps = (dispatch) => ({
    retrieveMemberNews: (token, history) => {
        dispatch(getMemberNews(token, history));
    }
});

export default connect(mapStateToProps, mapDispatchToProps)(MemberNewsPage);

Then, we replace the existing code in ‘client/src/components/MemberNewsPage.js’ with the code below:

import React, { Component } from 'react';
import { IsEmpty } from '../helpers';
import { memberNews } from '../reducers';

class MemberNewsPage extends Component {

    componentDidMount() {

        // Get the token in localStorage,
        // then dispatch the action to get the member only information.
        const token = localStorage.getItem('token');

        if (token !== null) {
            this.props.retrieveMemberNews(token, this.props.history);
        } else {
            this.props.history.push('/');
        }

    }

    render() {

        return (

            <div className="row">
                <div className="col col-md-3"></div>
                <div className="col col-md-6">
                    <div className="panel panel-info">
                        <div className="panel-heading">Member News</div>
                        <div className="panel-body">
                            { this.props.memberNews.length > 0 ?
                                <ul>
                                    {this.props.memberNews.map(function(newsItem, index) {
                                        return <li key={index}>{newsItem.title}</li>;
                                    })}
                                </ul>
                                :
                                <div>Sorry, there are no news for members.</div>
                            }
                        </div>
                    </div>
                </div>
                <div className="col col-md-3"></div>
            </div>

        );

    }

}

export default MemberNewsPage;

The code above is very straightforward. We try to get the JWT token in localStorage after MemberNewsPage component is mounted. If the token exists, we dispatch the getMemberNews action, otherwise we send the user to the home page.

Next, we need to use MemberNewsPageContainer component in our route instead of using MemberNewsPage component. So let’s open ‘client/src/App.js’ file and replace

import MemberNewsPage from './components/MemberNewsPage';

with

import MemberNewsPage from './components/MemberNewsPageContainer';

Then, we can add the member news page to the navigation menu as an item in the dropdown menu section for members. So let’s open ‘client/src/components/Navbar.js’ file and add the code below to it:

<li><Link to='/member-news'>Member News</Link></li>

Now, if you login and then go to the member news page, you can see that a list of member news items is showing up. But, if you do not login and try to access that page, you will be redirected to the home page.

Thank you for following along the series which talks about how we can use JSON Web Tokens for authentication and authorisation in React apps.

View the source code for this post on GitHub.

Please sign up to our Newsletter if you want to get notified when new tutorials are available.

Welcome back! This tutorial will be the last one in this series. In this tutorial, we will retrieve and display member news on the member news page. This tutorial will be broken down into following parts:

  • Create the route which handles the request for member only news
  • Create the memberNews reducer so that we can store member news as a state in the Redux store
  • Create the actions which respond to the member news request event
  • Update the MemberNewPage component so that it can trigger the event of making the request for member news

Create the route which handles the request for member only news

Let’s open the ‘app.js’ file and add the code below to it:

// get member news
app.post('/member-news', function(req, res, nex) {

    res.setHeader("Access-Control-Allow-Origin", "*");

    const token = req.body.token;

    jwt.verify(token, process.env.JWT_KEY, function(err, payload) {

        if (err) {
            return res.status(403).json({
                success: false,
                message: err.message
            });
        }

        const userId = payload.sub;

        User.findOne({
            '_id': userId
        }, function(err, user) {
            if (err) {
                return res.status(400).json({
                    success: false,
                    message: err.message
                });
            }

            if (user) {
                return res.json({
                    success: true,
                    message: 'The user has access to member news',
                    data: {
                        news: 
                            [
                                {
                                    title: 'member news 1 from the server',
                                    body: 'body of member news 1 from the server',
                                },
                                {
                                    title: 'member news 2 from the server',
                                    body: 'body of member news 2 from the server',
                                },
                                {
                                    title: 'member news 3 from the server',
                                    body: 'body of member news 3 from the server',
                                },
                                {
                                    title: 'member news 4 from the server',
                                    body: 'body of member news 4 from the server',
                                },
                                {
                                    title: 'member news 5 from the server',
                                    body: 'body of member news 5 from the server',
                                }
                            ]
                    }
                });
            } else {
                return res.status(401).json({
                    success: false,
                    message: 'The user does not have access to member news'
                });
            }
        });

    });

});

In the above code, we created a route ‘/member-news’ which handles the post request from the client side. The route retrieves the JWT token from the request body and then it verifies the token. If the token is valid, it then tries to find the corresponding user in the database. If the user exists, it returns the JSON object which contains member news.

Create the memberNews reducer so that we can store member news as a state in the Redux store

Member news returned from the server will be used in the member news page and it may also be used in other places in the future. So, it’s better to save it to our application state.  Let’s create a reducer for in the ‘client/src/reducers.js’ file:

export const memberNews = (state, action) => {

    switch (action.type) {
        case 'SET_MEMBER_NEWS': 
            return action.data;
        default: 
            return state || [];
    }

}

Create the actions which respond to the member news request event

Let’s open the ‘client/src/actions.js’ file and put the code below to it:

export const getMemberNews = (token, history) => {

    return dispatch => {
        fetch('http://localhost:3000/member-news', {
            method: 'post',
            mode: 'cors',
            headers: {
                "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"  
            },
            body: `token=${token}`
        })
        .then(response => {
            if (response.status >= 200 && response.status < 300) {
                return Promise.resolve(response);
            } else {
                return Promise.reject(new Error(response.statusText));
            }
        })
        .then(response => {
            return response.json();
        })
        .then(data => {
            if (data.success === true) {
                // Update the store state
                dispatch(setMemberNews(data.data.news));
            } else {
                 // Redirect to home page
                 history.push('/');
            }
        })
        .catch(err => {
            console.log(err);
        });
    }

};

export const setMemberNews = (news) => ({ type: 'SET_MEMBER_NEWS', data: news });

As you can see, in the above code, we created 2 actions: getMemberNews and setMemberNews. getmemberNews is an async action which makes a post request to the route we created earlier in this tutorial. Once we get the member news data from the server, we dispatch the setMemberNews action which passes the member news data to the memberNews reducer we created earlier in this tutorial.

Update the MemberNewsPage component so that it can trigger the event of making the request for member news

The MemberNewsPage component is responsible for following things:

  • Redirect users who are not logged in to the home page
  • Display member news for logged-in users

So, it needs to have the access to following data:

  • The current user in the application state
  • The member news in the application state

and it needs to do following things:

  • Dispatch the getMemberNews action

Based on our analysis, we need to create a container component and a presentational component for the member news page.

Let’s create the file ‘client/src/components/MemberNewsPageContainer.js’ and put the code below to it:

import React from 'react';
import { connect } from 'react-redux';
import { getMemberNews } from '../actions';
import MemberNewsPage from './MemberNewsPage';

const mapStateToProps = (state, ownProps) => ({
    cUser: state.cUser,
    memberNews: state.memberNews
});

const mapDispatchToProps = (dispatch) => ({
    retrieveMemberNews: (token, history) => {
        dispatch(getMemberNews(token, history));
    }
});

export default connect(mapStateToProps, mapDispatchToProps)(MemberNewsPage);

Then, we replace the existing code in ‘client/src/components/MemberNewsPage.js’ with the code below:

import React, { Component } from 'react';
import { IsEmpty } from '../helpers';
import { memberNews } from '../reducers';

class MemberNewsPage extends Component {

    componentDidMount() {

        // Get the token in localStorage,
        // then dispatch the action to get the member only information.
        const token = localStorage.getItem('token');

        if (token !== null) {
            this.props.retrieveMemberNews(token, this.props.history);
        } else {
            this.props.history.push('/');
        }

    }

    render() {

        return (

            <div className="row">
                <div className="col col-md-3"></div>
                <div className="col col-md-6">
                    <div className="panel panel-info">
                        <div className="panel-heading">Member News</div>
                        <div className="panel-body">
                            { this.props.memberNews.length > 0 ?
                                <ul>
                                    {this.props.memberNews.map(function(newsItem, index) {
                                        return <li key={index}>{newsItem.title}</li>;
                                    })}
                                </ul>
                                :
                                <div>Sorry, there are no news for members.</div>
                            }
                        </div>
                    </div>
                </div>
                <div className="col col-md-3"></div>
            </div>

        );

    }

}

export default MemberNewsPage;

The code above is very straightforward. We try to get the JWT token in localStorage after MemberNewsPage component is mounted. If the token exists, we dispatch the getMemberNews action, otherwise we send the user to the home page.

Next, we need to use MemberNewsPageContainer component in our route instead of using MemberNewsPage component. So let’s open ‘client/src/App.js’ file and replace

import MemberNewsPage from './components/MemberNewsPage';

with

import MemberNewsPage from './components/MemberNewsPageContainer';

Then, we can add the member news page to the navigation menu as an item in the dropdown menu section for members. So let’s open ‘client/src/components/Navbar.js’ file and add the code below to it:

<li><Link to='/member-news'>Member News</Link></li>

Now, if you go to the member news page as a logged-in user, you can see that member news are retrieved from the server and are displayed on this page. But if you try to access this page as a not-logged-in user, you will be redirected to the home page.

You can view the source code for this tutorial on GitHub.

Thank you for following along this series and hope you will find something useful for your own project.

Please sign up to our Newsletter if you want to get notified when new tutorials are available.

×