DEV Community

loading...

React: User Authentication + Django backend

bhuma08 profile image bhuma08 ・5 min read

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:

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';
Enter fullscreen mode Exit fullscreen mode

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:

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;
Enter fullscreen mode Exit fullscreen mode

In Actions/auth.js, add:

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,
            });
        });
};
Enter fullscreen mode Exit fullscreen mode

In Reducer/index.js, add:

import { combineReducers } from 'redux';
import AuthReducer from './auth';

const AllReducers = combineReducers({
  AuthReducer,
});

export default AllReducers;
Enter fullscreen mode Exit fullscreen mode

In store.js, add:

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;

Enter fullscreen mode Exit fullscreen mode

Make sure your original index.js looks like this:

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")
);
Enter fullscreen mode Exit fullscreen mode

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:

INSTALLED_APPS = [
    ...
    'corsheaders'
]

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

ALLOWED_HOSTS = ['*']

CORS_ORIGIN_ALLOW_ALL = True
Enter fullscreen mode Exit fullscreen mode

Now to allow the user to register on the browser, we need to make Register.js file and add:

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);
Enter fullscreen mode Exit fullscreen mode

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:

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);
Enter fullscreen mode Exit fullscreen mode

Make sure your App.js looks like this:

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;
Enter fullscreen mode Exit fullscreen mode

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:

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)
Enter fullscreen mode Exit fullscreen mode

Bring this Logout button to your Home file, like this:

import React, { Component } from 'react';
import Logout from './Logout.js';

class Home extends Component {
    render() {
        return (
            <div>
                <Logout/>
                Welcome
            </div>
        )
    }
}
export default Home;
Enter fullscreen mode Exit fullscreen mode

Discussion

pic
Editor guide