User Authentication: Register & Login using JWT in NextJs & MYSQL
In the earlier tutorial, you learnt data migration to synchronize Sequelize model with MYSQL database. This tutorial teaches you authentication using JWT (JSON Web Token) with MYSQL database.
The app allows a user to simply register an account using username, email, and password. The password is encrypted using bcryptjs. A successful registration will create a new record in users table of the MYSQL database.
To log in, the app requires a user to input username and password. After successful login, the app generates a token on server using jsonwebtoken. Then the token is returned to the client and stored at the client side using js-cookie for further API access.
Run the following commands to install bcrypt, jsonwebtoken, and js-cookie:
npm install bcrypt jsonwebtoken js-cookie
We build login and registration forms using reactstrap components. Thus, install the following dependencies:
npm install bootstrap reactstrap
In this project, we use axios to access server APIs and next-connect to do APIs method routing and middleware. Run the following commands to install axios and next-connect.
npm install axios next-connect
In the pages directory, create user directory. In the user directory, create three files: login.js, logout.js , and register.js.
login.js
import React, { useState } from "react" import axios from "axios"; import Link from 'next/link'; import Router, { useRouter } from 'next/router'; import Layout from "../../components/layout"; import Cookies from 'js-cookie'; import { Button, FormGroup,Card, CardHeader,CardBody,CardFooter, Input, Form, Label, } from "reactstrap"; const Login = (props) => { const [username, setUsername]=useState(''); const [password,setPassword]=useState(''); const [logerr,setLogError] = useState(''); const router = useRouter(); const [token, setToken]= useState(''); async function handleSubmit(e){ e.preventDefault(); axios({ method: 'post', url: `/api/auth`, data: {username: username,password:password}, headers: { "Content-type":"application/json",} }).then(response=>{ try { //console.log("token",response.data); let resobj=JSON.parse(JSON.stringify(response.data)); if(resobj.success){ //token valid in 30 days Cookies.set('token', resobj.token,{ expires: 30 }); // update token variable setToken(resobj.token); // redirect to home Router.push("/"); } else{ setLogError(resobj.error); } } catch (e) { setLogError('Error in login'); } }); } const handleUsernameInputChange = (event) => { setUsername(event.target.value); } const handlePasswordInputChange = (event) => { setPassword(event.target.value); } return ( <Layout title='login' token={token} > <Card className="my-2" style={{width: '25rem'}} > <CardHeader>Login</CardHeader> <Form className="form"> <CardBody> <FormGroup> <Input type="text" name="username" placeholder="Enter username" value={username} onChange={(e) =>handleUsernameInputChange(e)} required /> </FormGroup> <FormGroup> <Input type="password" name="password" placeholder="Enter password" value={password} onChange={(e) =>handlePasswordInputChange(e)} required /> </FormGroup> </CardBody> <CardFooter> <FormGroup> <Button color="primary" onClick={handleSubmit}>Login</Button> <Label style={{marginLeft: '5px'}}>Don't have an account?</Label> <Link href={{
pathname: '/user/register',}}>create user</Link> <Label>{logerr}</Label> </FormGroup> </CardFooter> </Form> </Card> </Layout> ) } export default Login
logout.js
import React,{useEffect, useState} from "react" import Router from 'next/router'; import Layout from "../../components/layout"; import Cookies from 'js-cookie'; const Logout = (props) => { const [token,setToken]=useState(''); useEffect(() => { Cookies.remove('token'); setToken(null); Router.push("/"); }, []); return ( <Layout token={token}> <div>Logout</div> </Layout> ) } export default Logout
register.js
import React, { useState } from "react" import axios from "axios"; import Router, { useRouter } from 'next/router'; import { Button, FormGroup,Card, CardHeader,CardBody,CardFooter, Input, Form, } from "reactstrap"; import Layout from "../../components/layout"; const Register = (props) => { const [errors,setErrors] = useState({}); const [inputs, setInputs] = useState({}); const router = useRouter(); // inputs validation function handleValidation() { let formIsValid = true; let es = {}; //Name if (!inputs.username) { formIsValid = false; es['username']="can not empty!"; } if (typeof inputs.username !== "undefined") { if (!inputs.username.match(/^[a-zA-Z0-9]+$/)) { formIsValid = false; es['username']="only letters and numbers allowed!"; } } // password if (!inputs.password) { formIsValid = false; es['password']="can not empty!"; } if (typeof inputs.password !== "undefined") { if (inputs.password.length<8) { formIsValid = false; es['password']="week password!"; } } //Email if (!inputs.email) { formIsValid = false; es['email']="can not empty!"; } if (typeof inputs.email!== "undefined") { if (!(/\S+@\S+\.\S+/.test(inputs.email))) { formIsValid = false; es['email']="invalid email!"; } } setErrors(es); return formIsValid; } function handleSubmit(e){ e.preventDefault(); if(handleValidation()){ axios({ method: 'post', url: `/api/register`, data: {username: inputs.username,password:inputs.password,email:inputs.email}, headers: { "Content-type":"application/json"} }).then(response=>{ try { console.log('new user=',response); const resobjt=JSON.parse(JSON.stringify(response)); if(resobjt.data.status === 'success'){ Router.push("/user/login"); } else{ alert('Failed to create user!'); } } catch (e) { alert('Failed to create user!'+e); } }); } } const handleChange = (event) =>{ const name = event.target.name; const value = event.target.value; setInputs(values => ({...values, [name]: value})) } return ( <Layout title='register user' > <Card className="my-2" style={{width: '18rem'}} > <CardHeader>Register</CardHeader> <Form className="form"> <CardBody> <FormGroup> <Input type="text" name="username" placeholder="Enter username" value={inputs.username || ""} onChange={handleChange} /> <span style={{color: '#ff2222'}}>{errors.username}</span> </FormGroup> <FormGroup> <Input type="email" name="email" placeholder="Enter email" value={inputs.email || ""} onChange={handleChange} /> <span style={{color: '#ff2222'}}>{errors.email}</span> </FormGroup> <FormGroup> <Input type="password" name="password" placeholder="Enter password" value={inputs.password || ""} onChange={handleChange} /> <span style={{color: '#ff2222'}}>{errors.password}</span> </FormGroup> </CardBody> <CardFooter> <FormGroup> <Button color="primary" onClick={handleSubmit}>Register</Button> </FormGroup> </CardFooter> </Form> </Card> </Layout> ) } export default Register
Run npm run dev to launch the nextjs app. On top menu, click login icon.
import nextConnect from 'next-connect'; import bcrypt from 'bcryptjs'; import db from '../../db/models/index';
const handler = nextConnect() .post(async (req, res) => { if(req.body){ const body = JSON.parse(req.body); const { username, email, password } = body; let password_encrypted= await bcrypt.hash(password, 10); const newUser = await db.User.create({ username:username, email:email, password:password_encrypted, }); return res.status(200).json({ status: 'success', message: 'done', data: newUser, }); } }); export default handler;
Save the project. Then, while MYSQL is running, register a new user account using username, email, and password. The new user should be created in the database.
import db from '../../db/models/index'; import nextConnect from 'next-connect'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; const KEY = process.env.JWT_KEY; const handler = nextConnect() .post(async (req, res) => { /* Get Post Data */ const body = JSON.parse(req.body); const { username, password } = body; /* Any how username or password is blank */ if (!username || !password) { return res.json({ status: 'error', error: 'Missing username or password!', }); } /* Check user in database */ const user = await db.User.findOne({ where: { username: username }, attributes: ['id', 'email', 'password'], limit: 1, }); /* Check if exists */ if (!user) { res.json({ status: 'error', error: 'User Not Found!' }); } /* Define variables */ const dataUser = user.toJSON(); const userId = dataUser.id, userEmail = dataUser.email, userPassword = dataUser.password; /* Check and compare password */ bcrypt.compare(password, userPassword).then(isMatch => { if (isMatch) { /* User matched */ /* Create JWT Payload */ const payload = { id: userId, email: userEmail, }; /* Sign token */ jwt.sign( payload, KEY, { expiresIn: 31556926, // 1 year in seconds }, (err, token) => { res.json({ success: true, token: token, }); }, ); } else { res.json({ status: 'error', error: 'Password incorrect!' }); } }); }); export default handler;
import jwt from 'jsonwebtoken'; const SECRET_KEY = process.env.JWT_KEY; //verify token export function verifyToken(jwtToken) { try { return jwt.verify(jwtToken, SECRET_KEY); } catch (e) { console.log('e:', e); return null; } } // get cookie export function getAppCookies(req) { const parsedItems = {}; if (req.headers.cookie) { const cookiesItems = req.headers.cookie.split('; '); cookiesItems.forEach(cookies => { const parsedItem = cookies.split('='); parsedItems[parsedItem[0]] = decodeURI(parsedItem[1]); }); } return parsedItems; }
import Layout from '../components/layout'; import { absoluteUrl, getAppCookies } from "../middleware/utils"; function Home(props) { const {token} = props; console.log('t=',props); return ( <Layout title='Home Page' token={token} /> ) } /* getServerSideProps */ export async function getServerSideProps(context) { const { req } = context; const token = getAppCookies(req).token || ''; return { props: { token }, }; } export default Home;
Comments
Post a Comment