React: User Authentication + Django backend
Published: 2020-11-23 19:04:32

So, in the previous blog, we successfully made API using django, where users are able to register and login by generating a token. Now, we need to implement this in our frontend to be used in the browser.

Create a `frontend` folder and `cd` into it
Install react in a new terminal: `npx create-react-app my-app` This creates another folder called `my-app` and dependencies are installed. This may take a while!

Once it's complete, `cd` into `my-app` and see if it works:`npm start`

Now, make a `Reducer` folder containing an `auth.js` file and `index.js`. You also need an `Actions` folder containing `types.js` and another `auth.js` file. Create a `store.js` file too.

In `Actions/types.js`, add:
```javascript
export const USER_LOADING = 'USER_LOADING';
export const USER_LOADED = 'USER_LOADED';
export const AUTH_ERROR = 'AUTH_ERROR';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_FAIL = 'LOGIN_FAIL';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const REGISTER_SUCCESS = 'REGISTER_SUCCESS';
export const REGISTER_FAIL = 'REGISTER_FAIL';
```
`npm install redux react-redux`
`npm install axios`
`npm install react-router-dom`
`npm install redux-devtools-extension`
`npm install redux-thunk`

In `Reducer/auth.js`, add:
```javascript
import {
    USER_LOADED,
    USER_LOADING,
    AUTH_ERROR,
    LOGIN_SUCCESS,
    LOGIN_FAIL,
    LOGOUT_SUCCESS,
    REGISTER_SUCCESS,
    REGISTER_FAIL
} from '../Actions/types';
  
const initialState = {
    token: localStorage.getItem('token'),
    isAuthenticated: null,
    isLoading: false,
    user: null,
};

function AuthReducer (state = initialState, action) {
    switch (action.type) {
        case USER_LOADING:
            return {...state,isLoading: true};
        case USER_LOADED:
            return {...state, isAuthenticated: true, isLoading: false, user: action.payload,};
        case LOGIN_SUCCESS:
        case REGISTER_SUCCESS:
        localStorage.setItem('token', action.payload.token);
        return {...state, ...action.payload, isAuthenticated: true,isLoading: false,};
        case AUTH_ERROR:
        case LOGIN_FAIL:
        case LOGOUT_SUCCESS:
        case REGISTER_FAIL:
            localStorage.removeItem('token');
            return {...state, token: null, user: null, isAuthenticated: false, isLoading: false,};
        default:
            return state;
    }
}

export default AuthReducer;
```
In `Actions/auth.js`, add:
```javascript
import axios from 'axios';
import {
    USER_LOADED,
    USER_LOADING,
    AUTH_ERROR,
    LOGIN_SUCCESS,
    LOGIN_FAIL,
    LOGOUT_SUCCESS,
    REGISTER_SUCCESS,
    REGISTER_FAIL,
} from './types';

// CHECK TOKEN & LOAD USER
export const loadUser = () => (dispatch, getState) => {
    // User Loading
    dispatch({ type: USER_LOADING });

    const token = getState().AuthReducer.token;

    const config = {
        headers :{
            'Content-Type': 'application/json',
        }
    }

    if(token){
        config.headers['Authorization']=`Token ${token}`
    }
  
    axios
        .get('http://127.0.0.1:8000/api/user/', config)
        .then((res) => {
            dispatch({
                type: USER_LOADED,
                payload: res.data,
            });
        })
        .catch((err) => {
            dispatch({
                type: AUTH_ERROR,
            });
        });
};

//Login
export const login = (username, password) => dispatch => {

    const config = {
        headers :{
            'Content-Type': 'application/json',
        }
    }

    //Request body
    const body= JSON.stringify({ username, password })
  
    axios
        .post('http://127.0.0.1:8000/api/login/', body, config)
        .then((res) => {
            dispatch({
                type: LOGIN_SUCCESS,
                payload: res.data,
            });
        })
        .catch((err) => {
            dispatch({
                type: LOGIN_FAIL,
            });
            alert("Invalid username or password")
        });
};

//Logout user
export const logout = () => (dispatch, getState) => {
    axios
        .post('http://127.0.0.1:8000/api/logout/', null, tokenConfig(getState))
        .then((res) => {
            dispatch({ type: 'CLEAR_LEADS' });
            dispatch({
                type: LOGOUT_SUCCESS,
            });
        })
        .catch((err) => {
        dispatch({
            type: AUTH_ERROR,
        });
    });
}

// Setup config with token - helper function
export const tokenConfig = (getState) => {
    // Get token from state
    const token = getState().AuthReducer.token;  
    // Headers
    const config = {
        headers: {
            'Content-Type': 'application/json',
        },
    };
    // If token, add to headers config
    if (token) {
      config.headers['Authorization'] = `Token ${token}`;
    }
    return config;
};

// REGISTER USER
export const register = ({ username, password }) => (dispatch) => {
    // Headers
    const config = {
        headers: {
            'Content-Type': 'application/json',
        },
    };
  
    // Request Body
    const body = JSON.stringify({ username, password });
  
    axios
        .post('http://127.0.0.1:8000/api/register/', body, config)
        .then((res) => {
            dispatch({
                type: REGISTER_SUCCESS,
                payload: res.data,
            });
        })
        .catch((err) => {
            dispatch({
                type: REGISTER_FAIL,
            });
        });
};
```
In `Reducer/index.js`, add:
```javascript
import { combineReducers } from 'redux';
import AuthReducer from './auth';

const AllReducers = combineReducers({
  AuthReducer,
});

export default AllReducers;
```

In `store.js`, add:
```javascript
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import AllReducers from "./Reducers";

const store = createStore(AllReducers, composeWithDevTools(applyMiddleware(thunk)));

export default store;

```
Make sure your original `index.js` looks like this:
```javascript
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./store";

ReactDOM.render(
  <BrowserRouter>
    <Provider store={store}>
      <App />
    </Provider>
  </BrowserRouter>,

  document.getElementById("root")
);
```
Your Login page should now work! If you get CORS error, make sure to install `pipenv install django-cors-headers` in your backend code and add the following in `settings.py`:

```python
INSTALLED_APPS = [
    ...
    'corsheaders'
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
]

ALLOWED_HOSTS = ['*']

CORS_ORIGIN_ALLOW_ALL = True
```
Now to allow the user to register on the browser, we need to make `Register.js` file and add:
```javascript
import React, { Component } from 'react'
import {connect} from "react-redux";
import PropTypes from 'prop-types';
import { register } from './Actions/auth.js';

class Register extends Component {

    state = {
        username: "",
        email:'',
        password: "",
        password2: '',
    }

    static propTypes ={
        register: PropTypes.func.isRequired,
    }

    
    onSubmit = e => {
        e.preventDefault();
        const { username, email, password, password2 } = this.state;

        if (password !== password2) {
            alert("Passwords do not match")
        } else if (username == '' || email == '' || password==''){
            alert("Please provide all the details")
        } else {
            const newUser = {
                username,
                password,
            };
            this.props.register(newUser);

            e.target.reset();
        
            alert("You have successfully registered!")
        }
    }
    
    render() {
        return (
            <div>
                <form onSubmit={this.onSubmit}>
                    <fieldset>
                        <legend>Register</legend>
                        <div>
                            <label>Username </label>
                            <input type="text" onChange={e => this.setState({username: e.target.value})} />
                        </div>

                        <div>
                            <label>Email </label>
                            <input type="text" onChange={e => this.setState({email: e.target.value})} />
                        </div>

                        <div>
                            <label>Password </label>
                            <input type="password" onChange={e => this.setState({password: e.target.value})} />
                        </div>

                        <div>
                            <label>Confirm Password </label>
                            <input type="password" onChange={e => this.setState({password2: e.target.value})} />
                        </div>
                            <button type="submit">Register</button>  
                    </fieldset>
                </form>    
            </div>
        )
    }
}
export default connect(null, { register })(Register);
```
After you create a `/register` route in your `App.js`, your register page should work.

Next, you need to make a `PrivateRoute.js` to make use users can only access the `/home` once they have successfully logged in!

In your `PrivateRoute.js` file, add:
```javascript
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';

const PrivateRoute = ({ component: Component, AuthReducer, ...rest }) => (
    <Route
        {...rest}
        render={(props) => {
            if (AuthReducer.isLoading) {
                return <h2>Loading...</h2>;
            } else if (!AuthReducer.isAuthenticated) {
                return <Redirect to="/login" />;
            } else {
                return <Component {...props} />;
            }
        }}
    />
);

const mapStateToProps = (state) => ({
    AuthReducer: state.AuthReducer,
});

export default connect(mapStateToProps)(PrivateRoute);
```
Make sure your `App.js` looks like this:
```javascript
import Login from './Login.js'
import Home from './Home.js'
import Register from './Register.js';
import PrivateRoute from './PrivateRoute.js'

function App() {
  return (
    <>
     <Switch>
        <PrivateRoute path="/home" component={Home} />
        <Route exact path="/login" component={Login} />
        <Route exact path="/register" component={Register} />
      </Switch>
    </>
  );
}

export default App;
```

Now, if you try to go to `/home` page without logging in first, it will redirect you to `/login` page.

Lastly, we need a log out button!
Create a `Logout.js` file and add:
```javascript
import React, { Component } from 'react'
import {connect} from "react-redux";
import PropTypes from 'prop-types';
import { logout } from './Actions/auth.js'

class Logout extends Component {

    static propTypes ={
        AuthReducer: PropTypes.object.isRequired,
        logout: PropTypes.func.isRequired
    }

    render() {

        const { isAuthenticated } = this.props.AuthReducer

        return (
            <div>
                {isAuthenticated ? <button onClick={this.props.logout}>Logout</button> : <h1></h1>}    
            </div>
        )
    }
}

const mSTP = (state) =>({
    AuthReducer: state.AuthReducer
})

export default connect(mSTP, { logout })(Logout)
```
Bring this `Logout` button to your `Home` file, like this:
```javascript
import React, { Component } from 'react';
import Logout from './Logout.js';

class Home extends Component {
    render() {
        return (
            <div>
                <Logout/>
                Welcome
            </div>
        )
    }
}
export default Home;
```