Skip to content

Commit ada415c

Browse files
committed
JWT Implementation
0 parents  commit ada415c

20 files changed

+1673
-0
lines changed

.env.example

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
DB_HOST=
2+
DB_DATABASE=
3+
DB_USERNAME=
4+
DB_PASSWORD=
5+
6+
JWT_SECRET=

.gitignore

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
/storage/outputs/*
4+
/storage/uploads/*
5+
# dependencies
6+
/node_modules
7+
/.pnp
8+
.pnp.js
9+
10+
# testing
11+
/coverage
12+
13+
# production
14+
/build
15+
16+
# misc
17+
.DS_Store
18+
.env
19+
.env.local
20+
.env.development.local
21+
.env.test.local
22+
.env.production.local
23+
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<p align="center"><img src="https://jwt.io/img/pic_logo.svg" width="100"></p>
2+
3+
### <h1 align="center" id="heading">JWT Authentication Implementation in Node.js</h1>
4+
5+
1. Copy `.env.example` to `.env`
6+
2. Update Database Credentials
7+
3. Create users table (node-jwt.sql)
8+
4. Update `JWT_SECRET` Key
9+
5. Test API's in Postman
10+
11+
Note:- You can use any random string as `JWT_SECRET`

app.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const express = require('express');
2+
const path = require('path');
3+
const cookieParser = require('cookie-parser');
4+
const logger = require('morgan');
5+
require('dotenv').config();
6+
7+
const indexRouter = require('./routes/index');
8+
const app = express();
9+
10+
app.use(logger('dev'));
11+
app.use(express.json());
12+
app.use(express.urlencoded({ extended: false }));
13+
app.use(cookieParser());
14+
app.use(express.static(path.join(__dirname, 'public')));
15+
16+
app.use('/api', indexRouter);
17+
18+
module.exports = app;

bin/www

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Module dependencies.
5+
*/
6+
7+
var app = require('../app');
8+
var debug = require('debug')('node-jwt:server');
9+
var http = require('http');
10+
11+
/**
12+
* Get port from environment and store in Express.
13+
*/
14+
15+
var port = normalizePort(process.env.PORT || '3000');
16+
app.set('port', port);
17+
18+
/**
19+
* Create HTTP server.
20+
*/
21+
22+
var server = http.createServer(app);
23+
24+
/**
25+
* Listen on provided port, on all network interfaces.
26+
*/
27+
28+
server.listen(port);
29+
server.on('error', onError);
30+
server.on('listening', onListening);
31+
32+
/**
33+
* Normalize a port into a number, string, or false.
34+
*/
35+
36+
function normalizePort(val) {
37+
var port = parseInt(val, 10);
38+
39+
if (isNaN(port)) {
40+
// named pipe
41+
return val;
42+
}
43+
44+
if (port >= 0) {
45+
// port number
46+
return port;
47+
}
48+
49+
return false;
50+
}
51+
52+
/**
53+
* Event listener for HTTP server "error" event.
54+
*/
55+
56+
function onError(error) {
57+
if (error.syscall !== 'listen') {
58+
throw error;
59+
}
60+
61+
var bind = typeof port === 'string'
62+
? 'Pipe ' + port
63+
: 'Port ' + port;
64+
65+
// handle specific listen errors with friendly messages
66+
switch (error.code) {
67+
case 'EACCES':
68+
console.error(bind + ' requires elevated privileges');
69+
process.exit(1);
70+
break;
71+
case 'EADDRINUSE':
72+
console.error(bind + ' is already in use');
73+
process.exit(1);
74+
break;
75+
default:
76+
throw error;
77+
}
78+
}
79+
80+
/**
81+
* Event listener for HTTP server "listening" event.
82+
*/
83+
84+
function onListening() {
85+
var addr = server.address();
86+
var bind = typeof addr === 'string'
87+
? 'pipe ' + addr
88+
: 'port ' + addr.port;
89+
debug('Listening on ' + bind);
90+
}

config/database.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
host: process.env.DB_HOST,
3+
database: process.env.DB_DATABASE,
4+
username: process.env.DB_USERNAME,
5+
password: process.env.DB_PASSWORD
6+
}

config/jwt.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* ttl - JWT time to live(seconds or time units(https://github.com/vercel/ms))
3+
* ttl: 3600 // 1 hour
4+
* ttl: '1h' // 1 hour
5+
* ttl: '7d' // 7 days
6+
*/
7+
module.exports = {
8+
secret: process.env.JWT_SECRET,
9+
ttl: 3600
10+
}

controllers/auth.controller.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const User = require('../models/user.model');
2+
const cache = require('../utils/cache');
3+
const jwtConfig = require('../config/jwt');
4+
const jwt = require('../utils/jwt');
5+
const bcrypt = require('bcrypt');
6+
7+
exports.register = async (req, res) => {
8+
const hashedPassword = await bcrypt.hash(req.body.password, 10);
9+
10+
const user = await User.create({
11+
name: req.body.name,
12+
email: req.body.email,
13+
password: hashedPassword
14+
});
15+
return res.json(user);
16+
}
17+
18+
exports.login = async (req, res) => {
19+
const user = await User.findOne({
20+
where: {
21+
email: req.body.email
22+
}
23+
});
24+
if (user) {
25+
const isMatched = await bcrypt.compare(req.body.password, user.password);
26+
if (isMatched) {
27+
const token = await jwt.createToken({ id: user.id });
28+
return res.json({
29+
access_token: token,
30+
token_type: 'Bearer',
31+
expires_in: jwtConfig.ttl
32+
});
33+
}
34+
}
35+
return res.status(401).json({ error: 'Unauthorized' });
36+
}
37+
38+
exports.getUser = async (req, res) => {
39+
const user = await User.findByPk(req.user.id);
40+
return res.json(user);
41+
}
42+
43+
exports.logout = async (req, res) => {
44+
let token = req.headers.authorization;
45+
if (token.startsWith('Bearer ')) {
46+
token = token.slice(7, token.length);
47+
}
48+
const decoded = await jwt.verifyToken(token.trim());
49+
50+
const now = new Date();
51+
const expire = new Date(decoded.exp);
52+
const milliseconds = now.getTime() - expire.getTime();
53+
/* ----------------------------- BlackList Token ---------------------------- */
54+
await cache.set(token, token, milliseconds);
55+
56+
return res.json({ message: 'Logged out successfully' });
57+
}

middleware/auth.guard.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const jwt = require('../utils/jwt');
2+
const cache = require('../utils/cache');
3+
4+
module.exports = async (req, res, next) => {
5+
6+
let token = req.headers.authorization;
7+
if (token && token.startsWith('Bearer ')) {
8+
token = token.slice(7, token.length);
9+
}
10+
11+
if (token) {
12+
try {
13+
/* ---------------------- Check For Blacklisted Tokens ---------------------- */
14+
const isBlackListed = await cache.get(token.trim());
15+
if (isBlackListed) {
16+
return res.status(401).json({ error: 'Unauthorized' });
17+
}
18+
19+
const decoded = await jwt.verifyToken(token.trim());
20+
req.user = decoded;
21+
next();
22+
23+
} catch (error) {
24+
return res.status(401).json({ error: 'Unauthorized' });
25+
}
26+
} else {
27+
return res.status(400).json({ error: 'Authorization header is missing.' })
28+
}
29+
}

models/connection.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const { Sequelize } = require('sequelize');
2+
const config = require('../config/database');
3+
4+
const sequelize = new Sequelize(config.database, config.username, config.password, {
5+
host: config.host,
6+
dialect: 'mysql',
7+
operatorsAliases: 'false',
8+
logging: false
9+
});
10+
11+
module.exports = sequelize

models/user.model.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const { DataTypes } = require('sequelize');
2+
const sequelize = require('./connection');
3+
4+
const User = sequelize.define('User', {
5+
name: {
6+
type: DataTypes.STRING
7+
},
8+
email: {
9+
type: DataTypes.STRING
10+
},
11+
password: {
12+
type: DataTypes.STRING
13+
}
14+
}, {
15+
createdAt: 'created_at',
16+
updatedAt: 'updated_at'
17+
});
18+
module.exports = User;

node-jwt.sql

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
-- phpMyAdmin SQL Dump
2+
-- version 5.1.1
3+
-- https://www.phpmyadmin.net/
4+
--
5+
-- Host: 127.0.0.1
6+
-- Generation Time: Mar 18, 2022 at 11:07 AM
7+
-- Server version: 10.4.22-MariaDB
8+
-- PHP Version: 7.4.26
9+
10+
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
11+
START TRANSACTION;
12+
SET time_zone = "+00:00";
13+
14+
15+
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
16+
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
17+
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
18+
/*!40101 SET NAMES utf8mb4 */;
19+
20+
--
21+
-- Database: `node-jwt`
22+
--
23+
24+
-- --------------------------------------------------------
25+
26+
--
27+
-- Table structure for table `users`
28+
--
29+
30+
CREATE TABLE `users` (
31+
`id` bigint(20) UNSIGNED NOT NULL,
32+
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
33+
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
34+
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
35+
`created_at` timestamp NULL DEFAULT NULL,
36+
`updated_at` timestamp NULL DEFAULT NULL
37+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
38+
39+
--
40+
-- Dumping data for table `users`
41+
--
42+
43+
INSERT INTO `users` (`id`, `name`, `email`, `password`, `created_at`, `updated_at`) VALUES
44+
(1, 'Akash Verma', '[email protected]', '$2b$10$Rlme91E11wfRumYjPfw07.8MXOXQDVMC1bPWWvv4PczJohwnGLTNC', '2022-03-18 08:49:40', '2022-03-18 08:49:40');
45+
46+
--
47+
-- Indexes for dumped tables
48+
--
49+
50+
--
51+
-- Indexes for table `users`
52+
--
53+
ALTER TABLE `users`
54+
ADD PRIMARY KEY (`id`),
55+
ADD UNIQUE KEY `users_email_unique` (`email`);
56+
57+
--
58+
-- AUTO_INCREMENT for dumped tables
59+
--
60+
61+
--
62+
-- AUTO_INCREMENT for table `users`
63+
--
64+
ALTER TABLE `users`
65+
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
66+
COMMIT;
67+
68+
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
69+
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
70+
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

0 commit comments

Comments
 (0)