Berikan support Anda dengan cara mentrakteer saya kopi!

Trakteer Sekarang.!

Login dan Registrasi Menggunakan JWT (MERN Stack)

Login dan Registrasi Menggunakan JWT (MERN Stack)

Pada tutorial ini Anda akan belajar bagaimana membuat login dan registrasi menggunakan JWT (JSON Web Token) dengan node js, express, mysql di sisi backend dan react js di frontend.

Tidak hanya itu,

Saya juga akan berbagi kepada Anda bagaimana membuat refresh token dan menyimpan refresh token ke dalam httpOnly cookie sehingga aplikasi kita akan aman dari serangan XSS (Cross-site Scripting) dan CSRF (Cross-site Request Forgery).

Mari kita mulai.

 

Step #1. Backend

#1.1. Install Dependency

Buat sebuah folder di komputer Anda, di sini saya beri nama “jwt-auth”.

Anda bebas membuatnya di manapun, baik di C, D, ataupun di Desktop.

Kemudian buka folder “jwt-auth” tersebut menggunakan code editor, disini saya menggunakan Visual Studio Code.

Saya juga menyarankan Anda untuk menggunakan Visual Studio Code.

Anda dapat mendownload Visual Studio Code pada link berikut, kemudian instal di komputer Anda:

https://code.visualstudio.com/

Setelah folder “jwt-auth” ter-open menggunakan Visual Studio Code, buat sebuah sub folder bernama “backend” di dalam folder “jwt-auth”.

Selanjutnya, buka terminal pada Visual Studio Code pada menu bar terminal => new terminal.

Setelah itu, ketikkan perintah berikut pada terminal untuk membuat file “package.json”:

npm init -y

Selanjutnya, install express, mysql2, sequelize, jsonwebtoken, bcrypt, cookie-parser, dotenv dan cors dengan mengetikan perintah berikut pada terminal:

npm install express mysql2 sequelize jsonwebtoken bcrypt cookie-parser dotenv cors

Selanjutnya, install nodemon secara global dengan mengetikan perintah berikut pada terminal:

npm install -g nodemon

Selanjutnya, tambahkan kode berikut pada file “package.json”:

"type": "module",

Sehingga file “package.json” terlihat menjadi seperti berikut:

{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^5.0.1",
    "cookie-parser": "^1.4.5",
    "cors": "^2.8.5",
    "dotenv": "^10.0.0",
    "express": "^4.17.1",
    "jsonwebtoken": "^8.5.1",
    "mysql2": "^2.3.2",
    "sequelize": "^6.8.0"
  }
}

Hal ini bertujuan agar kita dapat menggunakan ES6 Module Syntax untuk export dan import module.

Dapatkan diskon 75% paket hosting dan gratis domain + extra diskon 5% dengan menggunakan kupon: MFIKRI

Order Sekarang.!

 

#1.2. Buat Database

Untuk dapat menggunakan MySQL, Anda perlu menginstall XAMPP, WAMP, MAMP, atau software sejenisnya.

Pada tutorial ini, saya menggunakan XAMPP.

Kemudian buat database baru pada MySQL, Anda dapat menggunakan tools seperti SQLyog, PHPMyAdmin atau sejenisnya.

Disini saya membuat database dengan nama “auth_db”.

Jika Anda membuat database dengan nama yang sama, itu lebih baik.

Untuk  membuat database pada MySQL, dapat dilakukan dengan mengeksekusi query berikut:

CREATE DATABASE auth_db;

Perintah SQL diatas akan membuat sebuah database dengan nama “auth_db”.

 

#1.3. Struktur Aplikasi

Agar aplikasi lebih terstruktur rapi, kita akan menerapkan pola MVC (Model-View-Controllers).

Buat folder “config”, “controllers”, “middleware”, “models”, dan “routes” di dalam folder “backend”.

Kemudian buat file “Database.js” di dalam folder “config”, buat file “Users.js” dan “RefreshToken.js” di dalam folder “controllers”, buat file “VerifyToken.js” di dalam folder “middleware”, buat file “UserModel.js” di dalam folder “models”, buat file “index.js” di dalam folder “routes”, dan buat file “index.js” di dalam folder “backend”.

Perhatikan gambar berikut untuk lebih jelasnya:

struktur project

 

#1.4. Connect ke Database

Buka file “Database.js” yang terdapat pada folder “config”, kemudian ketikan kode berikut:

import { Sequelize } from "sequelize";

const db = new Sequelize('auth_db', 'root', '', {
    host: "localhost",
    dialect: "mysql"
});

export default db;

 

#1.5. Models

Buka file model “UserModel.js” yang terdapat pada folder “models”, kemudian ketikan kode berikut:

import { Sequelize } from "sequelize";
import db from "../config/Database.js";

const { DataTypes } = Sequelize;

const Users = db.define('users',{
    name:{
        type: DataTypes.STRING
    },
    email:{
        type: DataTypes.STRING
    },
    password:{
        type: DataTypes.STRING
    },
    refresh_token:{
        type: DataTypes.TEXT
    }
},{
    freezeTableName:true
});

(async () => {
    await db.sync();
})();

export default Users;

 

#1.6. Controllers

Buka file controller “Users.js” yang terdapat pada folder “controllers”, kemudian ketikan kode berikut:

import Users from "../models/UserModel.js";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";

export const getUsers = async(req, res) => {
    try {
        const users = await Users.findAll({
            attributes:['id','name','email']
        });
        res.json(users);
    } catch (error) {
        console.log(error);
    }
}

export const Register = async(req, res) => {
    const { name, email, password, confPassword } = req.body;
    if(password !== confPassword) return res.status(400).json({msg: "Password dan Confirm Password tidak cocok"});
    const salt = await bcrypt.genSalt();
    const hashPassword = await bcrypt.hash(password, salt);
    try {
        await Users.create({
            name: name,
            email: email,
            password: hashPassword
        });
        res.json({msg: "Register Berhasil"});
    } catch (error) {
        console.log(error);
    }
}

export const Login = async(req, res) => {
    try {
        const user = await Users.findAll({
            where:{
                email: req.body.email
            }
        });
        const match = await bcrypt.compare(req.body.password, user[0].password);
        if(!match) return res.status(400).json({msg: "Wrong Password"});
        const userId = user[0].id;
        const name = user[0].name;
        const email = user[0].email;
        const accessToken = jwt.sign({userId, name, email}, process.env.ACCESS_TOKEN_SECRET,{
            expiresIn: '20s'
        });
        const refreshToken = jwt.sign({userId, name, email}, process.env.REFRESH_TOKEN_SECRET,{
            expiresIn: '1d'
        });
        await Users.update({refresh_token: refreshToken},{
            where:{
                id: userId
            }
        });
        res.cookie('refreshToken', refreshToken,{
            httpOnly: true,
            maxAge: 24 * 60 * 60 * 1000
        });
        res.json({ accessToken });
    } catch (error) {
        res.status(404).json({msg:"Email tidak ditemukan"});
    }
}

export const Logout = async(req, res) => {
    const refreshToken = req.cookies.refreshToken;
    if(!refreshToken) return res.sendStatus(204);
    const user = await Users.findAll({
        where:{
            refresh_token: refreshToken
        }
    });
    if(!user[0]) return res.sendStatus(204);
    const userId = user[0].id;
    await Users.update({refresh_token: null},{
        where:{
            id: userId
        }
    });
    res.clearCookie('refreshToken');
    return res.sendStatus(200);
}

Setelah itu, buka file controller “RefreshToken.js” yang terdapat pada folder “controllers”, kemudian ketikan kode berikut:

import Users from "../models/UserModel.js";
import jwt from "jsonwebtoken";

export const refreshToken = async(req, res) => {
    try {
        const refreshToken = req.cookies.refreshToken;
        if(!refreshToken) return res.sendStatus(401);
        const user = await Users.findAll({
            where:{
                refresh_token: refreshToken
            }
        });
        if(!user[0]) return res.sendStatus(403);
        jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, decoded) => {
            if(err) return res.sendStatus(403);
            const userId = user[0].id;
            const name = user[0].name;
            const email = user[0].email;
            const accessToken = jwt.sign({userId, name, email}, process.env.ACCESS_TOKEN_SECRET,{
                expiresIn: '15s'
            });
            res.json({ accessToken });
        });
    } catch (error) {
        console.log(error);
    }
}

 

#1.7. Middleware

Buka file “VerifyToken.js” yang terdapat pada folder “middleware”, kemudian ketikan kode berikut:

import jwt from "jsonwebtoken";

export const verifyToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];
    if(token == null) return res.sendStatus(401);
    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, decoded) => {
        if(err) return res.sendStatus(403);
        req.email = decoded.email;
        next();
    })
}

 

#1.8. Routes

Buka file “index.js” yang terdapat pada folder “routes”, kemudian ketikan kode berikut:

import express from "express";
import { getUsers, Register, Login, Logout } from "../controllers/Users.js";
import { verifyToken } from "../middleware/VerifyToken.js";
import { refreshToken } from "../controllers/RefreshToken.js";

const router = express.Router();

router.get('/users', verifyToken, getUsers);
router.post('/users', Register);
router.post('/login', Login);
router.get('/token', refreshToken);
router.delete('/logout', Logout);

export default router;

 

#1.9. Entry Point

Buka file “index.js” yang terdapat pada folder “backend”, kemudian ketikan kode berikut:

import express from "express";
import dotenv from "dotenv";
import cookieParser from "cookie-parser";
import cors from "cors";
import db from "./config/Database.js";
import router from "./routes/index.js";
dotenv.config();
const app = express();

app.use(cors({ credentials:true, origin:'http://localhost:3000' }));
app.use(cookieParser());
app.use(express.json());
app.use(router);

app.listen(5000, ()=> console.log('Server running at port 5000'));

 

#1.10. .env

Buat sebuah file bernama “.env” di dalam folder “backend”, kemudian ketikan kode berikut:

ACCESS_TOKEN_SECRET = jsfgfjguwrg8783wgbjs849h2fu3cnsvh8wyr8fhwfvi2g225
REFRESH_TOKEN_SECRET = 825y8i3hnfjmsbv7gwajbl7fobqrjfvbs7gbfj2q3bgh8f42

Anda bebas memasukan value pada ACCESS_TOKEN_SECRET dan REFRESH_TOKEN_SECRET diatas.

Untuk memastikan aplikasi berjalan dengan baik, jalankan aplikasi dengan mengetikan perintah berikut pada terminal:

nodemon index

Jika berjalan dengan baik, maka akan terlihat seperti gambar berikut:

nodemon

Sampai disini Anda telah berhasil membuat “backend”.

 

Step #2. Frontend

#2.1. Create React App

Untuk front-end, saya akan menggunakan React JS.

Jika Anda belum familiar dengan React JS, saya sarankan Anda untuk mempelajari “Tutorial React JS Untuk Pemula” terlebih dahulu.

Untuk membuat project react js, dapat dilakukan dengan banyak cara. Akan tetapi cara yang paling mudah adalah “create react app”.

Buka new terminal dan buat project react baru dengan mengetikan perintah berikut pada terminal:

npx create-react-app frontend

Dan pastikan Anda berada di folder “jwt-auth”, seperti gambar berikut:

react-app

Jika instalasi selesai, maka akan terdapat folder “frontend” di dalam folder “jwt-auth”.

Sehingga di dalam folder “jwt-auth” terdapat dua folder yaitu: “backend” dan “frontend”.

Selanjutnya, masuk kedalam folder “frontend” dengan mengetikan perintah berikut pada terminal:

cd frontend

Setelah itu, install react-router-dom, axios, jwt-decode, dan bulma dengan mengetikan perintah berikut pada terminal:

npm install react-router-dom axios jwt-decode bulma

Setelah instalasi selesai, dan untuk memastikan semuanya berjalan dengan baik, ketikan perintah berikut untuk menjalankan project:

npm start

Jika berjalan dengan baik, maka akan tampil seperti gambar berikut:

npm-start

 

#2.2. Components

Buat sebuah folder bernama “components” di dalam folder “frontend/src”.

Kemudian, buat file components “Dashboard.js”, “Login.js”, “Navbar.js”, dan “Register.js” pada folder “frontend/src/components”.

Perhatikan gambar berikut untuk lebih jelasnya:

frontend-structure

 

#2.3. Registrasi

Buka file “Register.js” yang terdapat pada folder “frontend/src”, kemudian ketikan kode berikut:

import React, { useState } from 'react'
import axios from "axios";
import { useHistory } from "react-router-dom";

const Register = () => {
    const [name, setName] = useState('');
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [confPassword, setConfPassword] = useState('');
    const [msg, setMsg] = useState('');
    const history = useHistory();

    const Register = async (e) => {
        e.preventDefault();
        try {
            await axios.post('http://localhost:5000/users', {
                name: name,
                email: email,
                password: password,
                confPassword: confPassword
            });
            history.push("/");
        } catch (error) {
            if (error.response) {
                setMsg(error.response.data.msg);
            }
        }
    }

    return (
        <section className="hero has-background-grey-light is-fullheight is-fullwidth">
            <div className="hero-body">
                <div className="container">
                    <div className="columns is-centered">
                        <div className="column is-4-desktop">
                            <form onSubmit={Register} className="box">
                                <p className="has-text-centered">{msg}</p>
                                <div className="field mt-5">
                                    <label className="label">Name</label>
                                    <div className="controls">
                                        <input type="text" className="input" placeholder="Name"
                                            value={name} onChange={(e) => setName(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <label className="label">Email</label>
                                    <div className="controls">
                                        <input type="text" className="input" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <label className="label">Password</label>
                                    <div className="controls">
                                        <input type="password" className="input" placeholder="******" value={password} onChange={(e) => setPassword(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <label className="label">Confirm Password</label>
                                    <div className="controls">
                                        <input type="password" className="input" placeholder="******" value={confPassword} onChange={(e) => setConfPassword(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <button className="button is-success is-fullwidth">Register</button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    )
}

export default Register

 

#2.4. Login

Buka file “Login.js” yang terdapat pada folder “frontend/src”, kemudian ketikan kode berikut:

import React, { useState } from 'react'
import axios from 'axios';
import { useHistory } from 'react-router-dom';

const Login = () => {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [msg, setMsg] = useState('');
    const history = useHistory();

    const Auth = async (e) => {
        e.preventDefault();
        try {
            await axios.post('http://localhost:5000/login', {
                email: email,
                password: password
            });
            history.push("/dashboard");
        } catch (error) {
            if (error.response) {
                setMsg(error.response.data.msg);
            }
        }
    }

    return (
        <section className="hero has-background-grey-light is-fullheight is-fullwidth">
            <div className="hero-body">
                <div className="container">
                    <div className="columns is-centered">
                        <div className="column is-4-desktop">
                            <form onSubmit={Auth} className="box">
                                <p className="has-text-centered">{msg}</p>
                                <div className="field mt-5">
                                    <label className="label">Email or Username</label>
                                    <div className="controls">
                                        <input type="text" className="input" placeholder="Username" value={email} onChange={(e) => setEmail(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <label className="label">Password</label>
                                    <div className="controls">
                                        <input type="password" className="input" placeholder="******" value={password} onChange={(e) => setPassword(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <button className="button is-success is-fullwidth">Login</button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    )
}

export default Login

 

#2.5. Dashboard

Buka file “Dashboard.js” yang terdapat pada folder “frontend/src”, kemudian ketikan kode berikut:

/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from 'react'
import axios from 'axios';
import jwt_decode from "jwt-decode";
import { useHistory } from 'react-router-dom';

const Dashboard = () => {
    const [name, setName] = useState('');
    const [token, setToken] = useState('');
    const [expire, setExpire] = useState('');
    const [users, setUsers] = useState([]);
    const history = useHistory();

    useEffect(() => {
        refreshToken();
        getUsers();
    }, []);

    const refreshToken = async () => {
        try {
            const response = await axios.get('http://localhost:5000/token');
            setToken(response.data.accessToken);
            const decoded = jwt_decode(response.data.accessToken);
            setName(decoded.name);
            setExpire(decoded.exp);
        } catch (error) {
            if (error.response) {
                history.push("/");
            }
        }
    }

    const axiosJWT = axios.create();

    axiosJWT.interceptors.request.use(async (config) => {
        const currentDate = new Date();
        if (expire * 1000 < currentDate.getTime()) {
            const response = await axios.get('http://localhost:5000/token');
            config.headers.Authorization = `Bearer ${response.data.accessToken}`;
            setToken(response.data.accessToken);
            const decoded = jwt_decode(response.data.accessToken);
            setName(decoded.name);
            setExpire(decoded.exp);
        }
        return config;
    }, (error) => {
        return Promise.reject(error);
    });

    const getUsers = async () => {
        const response = await axiosJWT.get('http://localhost:5000/users', {
            headers: {
                Authorization: `Bearer ${token}`
            }
        });
        setUsers(response.data);
    }

    return (
        <div className="container mt-5">
            <h1>Welcome Back: {name}</h1>
            <table className="table is-striped is-fullwidth">
                <thead>
                    <tr>
                        <th>No</th>
                        <th>Name</th>
                        <th>Email</th>
                    </tr>
                </thead>
                <tbody>
                    {users.map((user, index) => (
                        <tr key={user.id}>
                            <td>{index + 1}</td>
                            <td>{user.name}</td>
                            <td>{user.email}</td>
                        </tr>
                    ))}

                </tbody>
            </table>
        </div>
    )
}

export default Dashboard

 

#2.6. Navbar

Buka file “Navbar.js” yang terdapat pada folder “frontend/src”, kemudian ketikan kode berikut:

import React from 'react'
import axios from 'axios';
import { useHistory } from 'react-router-dom';

const Navbar = () => {
    const history = useHistory();

    const Logout = async () => {
        try {
            await axios.delete('http://localhost:5000/logout');
            history.push("/");
        } catch (error) {
            console.log(error);
        }
    }

    return (
        <nav className="navbar is-light" role="navigation" aria-label="main navigation">
            <div className="container">
                <div className="navbar-brand">
                    <a className="navbar-item" href="https://bulma.io">
                        <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28" alt="logo" />
                    </a>

                    <a href="/" role="button" className="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
                        <span aria-hidden="true"></span>
                        <span aria-hidden="true"></span>
                        <span aria-hidden="true"></span>
                    </a>
                </div>

                <div id="navbarBasicExample" className="navbar-menu">
                    <div className="navbar-start">
                        <a href="/" className="navbar-item">
                            Home
                        </a>
                    </div>

                    <div className="navbar-end">
                        <div className="navbar-item">
                            <div className="buttons">
                                <button onClick={Logout} className="button is-light">
                                    Log Out
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </nav>
    )
}

export default Navbar

 

#2.7. App.js

Buka file “App.js” yang terdapat pada folder “frontend”, kemudian ubah menjadi seperti berikut:

import { BrowserRouter, Route, Switch } from "react-router-dom";
import Dashboard from "./components/Dashboard";
import Login from "./components/Login";
import Navbar from "./components/Navbar";
import Register from "./components/Register";

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/">
          <Login/>
        </Route>
        <Route path="/register">
          <Register/>
        </Route>
        <Route path="/dashboard">
          <Navbar/>
          <Dashboard/>
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

export default App;

 

#2.8. index.js

Buka file “index.js” yang terdapat pada folder “frontend”, kemudian ubah menjadi seperti berikut:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import "bulma/css/bulma.css";
import axios from "axios";

axios.defaults.withCredentials = true;

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

 

Step #3. Testing

Kembali ke browser dan kunjungi URL berikut:

http://localhost:3000/register

Jika berjalan dengan baik, maka akan terlihat seperti gambar berikut:

register

Masukkan name, email, password, dan confirm password, kemudian klik tombol Register.

Jika berjalan dengan baik, maka akan diarahkan ke form login seperti berikut:

login

Masukan username dan password, kemudian klik tombol Login.

Jika berjalan dengan baik, maka akan diarahkan ke halaman dashboard seperti berikut:

dashboard

Untuk logout, klik tombol “Log Out”, maka akan diarahkan ke form login.

 

Kesimpulan:

Pembahasan kali ini adalah bagaimana membuat Login dan Registrasi menggunakan JWT (JSON Web Token) dengan node js, express, react js, dan MySQL.

Tidak hanya itu, Anda juga telah belajar bagaimana membuat refresh token sehingga disaat access token expired, user tidak perlu login lagi.

Jadi tunggu apalagi, Let’s Coding!

Dapatkan diskon 75% paket hosting dan gratis domain + extra diskon 5% dengan menggunakan kupon: MFIKRI

Order Sekarang.!
Download Source Code (Back-End) Download Source Code (Front-End)

Komentar (2)

Nisaa, 13 April 2022 15:05 - Reply

Yang bagian backend pas jalanin nodemon index selalu muncul "nodemon is not recognized as the name of a cmdlet,..." padahal udah install nodemon global. Kenapa tu ya bang?

farhan, 07 June 2022 14:12 - Reply

terimakasih ka, sangat membantu sekali

Leave a Comment