├── .envExample ├── README.md ├── config ├── connectdb.js └── emailConfig.js ├── models └── User.js ├── package.json ├── app.js ├── routes └── userRoutes.js ├── middlewares └── auth-middleware.js ├── .gitignore ├── PostmanEndpoints └── ExpressAuthJWTAPI.postman_collection.json └── controllers └── userController.js /.envExample: -------------------------------------------------------------------------------- 1 | PORT = 8000 2 | DATABASE_URL = "mongodb://localhost:27017" 3 | 4 | JWT_SECRET_KEY = "dhsjf3423jhsdf3423df" 5 | 6 | EMAIL_HOST = 'smtp.gmail.com' 7 | EMAIL_PORT = 587 8 | EMAIL_USER = 'contact@geekyshows.com' 9 | EMAIL_PASS = '123456' 10 | EMAIL_FROM = 'contact@geekyshows.com' 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Express JS Node JS Complete Authentication API 2 | ### Video Link:- https://youtu.be/2pn2Bspt6EM 3 | 4 | ## To Run this Project via NPM follow below: 5 | 6 | ```bash 7 | npm install 8 | npm run dev 9 | ``` 10 | 11 | #### There is a Folder "PostmanEndpoints" which has Postman Collection File You can import this file in your postman to test this API 12 | 13 | -------------------------------------------------------------------------------- /config/connectdb.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const connectDB = async (DATABASE_URL) => { 4 | try { 5 | const DB_OPTIONS = { 6 | dbName: "geekshop" 7 | } 8 | await mongoose.connect(DATABASE_URL, DB_OPTIONS) 9 | console.log('Connected Successfully...') 10 | } catch (error) { 11 | console.log(error) 12 | } 13 | } 14 | 15 | export default connectDB -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | // Defining Schema 4 | const userSchema = new mongoose.Schema({ 5 | name: { type: String, required: true, trim: true }, 6 | email: { type: String, required: true, trim: true }, 7 | password: { type: String, required: true, trim: true }, 8 | tc: { type: Boolean, required: true } 9 | }) 10 | 11 | // Model 12 | const UserModel = mongoose.model("user", userSchema) 13 | 14 | export default UserModel -------------------------------------------------------------------------------- /config/emailConfig.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv' 2 | dotenv.config() 3 | import nodemailer from 'nodemailer' 4 | 5 | let transporter = nodemailer.createTransport({ 6 | host: process.env.EMAIL_HOST, 7 | port: process.env.EMAIL_PORT, 8 | secure: false, // true for 465, false for other ports 9 | auth: { 10 | user: process.env.EMAIL_USER, // Admin Gmail ID 11 | pass: process.env.EMAIL_PASS, // Admin Gmail Password 12 | }, 13 | }) 14 | 15 | export default transporter -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expressauthjwt", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node app", 8 | "dev": "nodemon app" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "type": "module", 14 | "dependencies": { 15 | "bcrypt": "^5.0.1", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.0.0", 18 | "express": "^5.0.0-beta.1", 19 | "jsonwebtoken": "^8.5.1", 20 | "mongoose": "^6.2.3", 21 | "nodemailer": "^6.7.2" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^2.0.15" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv' 2 | dotenv.config() 3 | import express from 'express' 4 | import cors from 'cors'; 5 | import connectDB from './config/connectdb.js' 6 | import userRoutes from './routes/userRoutes.js' 7 | 8 | const app = express() 9 | const port = process.env.PORT 10 | const DATABASE_URL = process.env.DATABASE_URL 11 | 12 | // CORS Policy 13 | app.use(cors()) 14 | 15 | // Database Connection 16 | connectDB(DATABASE_URL) 17 | 18 | // JSON 19 | app.use(express.json()) 20 | 21 | // Load Routes 22 | app.use("/api/user", userRoutes) 23 | 24 | app.listen(port, () => { 25 | console.log(`Server listening at http://localhost:${port}`) 26 | }) -------------------------------------------------------------------------------- /routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import UserController from '../controllers/userController.js'; 4 | import checkUserAuth from '../middlewares/auth-middleware.js'; 5 | 6 | // ROute Level Middleware - To Protect Route 7 | router.use('/changepassword', checkUserAuth) 8 | router.use('/loggeduser', checkUserAuth) 9 | 10 | // Public Routes 11 | router.post('/register', UserController.userRegistration) 12 | router.post('/login', UserController.userLogin) 13 | router.post('/send-reset-password-email', UserController.sendUserPasswordResetEmail) 14 | router.post('/reset-password/:id/:token', UserController.userPasswordReset) 15 | 16 | // Protected Routes 17 | router.post('/changepassword', UserController.changeUserPassword) 18 | router.get('/loggeduser', UserController.loggedUser) 19 | 20 | 21 | export default router -------------------------------------------------------------------------------- /middlewares/auth-middleware.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | import UserModel from '../models/User.js' 3 | 4 | var checkUserAuth = async (req, res, next) => { 5 | let token 6 | const { authorization } = req.headers 7 | if (authorization && authorization.startsWith('Bearer')) { 8 | try { 9 | // Get Token from header 10 | token = authorization.split(' ')[1] 11 | 12 | // Verify Token 13 | const { userID } = jwt.verify(token, process.env.JWT_SECRET_KEY) 14 | 15 | // Get User from Token 16 | req.user = await UserModel.findById(userID).select('-password') 17 | 18 | next() 19 | } catch (error) { 20 | console.log(error) 21 | res.status(401).send({ "status": "failed", "message": "Unauthorized User" }) 22 | } 23 | } 24 | if (!token) { 25 | res.status(401).send({ "status": "failed", "message": "Unauthorized User, No Token" }) 26 | } 27 | } 28 | 29 | export default checkUserAuth -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /PostmanEndpoints/ExpressAuthJWTAPI.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "1da9f626-32f4-481a-9451-1d822eed927e", 4 | "name": "ExpressAuthJWTAPI", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "User Registration", 10 | "request": { 11 | "method": "POST", 12 | "header": [ 13 | { 14 | "key": "Accept", 15 | "value": "application/json", 16 | "type": "default" 17 | } 18 | ], 19 | "body": { 20 | "mode": "raw", 21 | "raw": "{\r\n \"name\":\"Sonam\",\r\n \"email\":\"sonam@example.com\",\r\n \"password\":\"123456\",\r\n \"password_confirmation\":\"123456\",\r\n \"tc\":true\r\n}", 22 | "options": { 23 | "raw": { 24 | "language": "json" 25 | } 26 | } 27 | }, 28 | "url": { 29 | "raw": "http://localhost:8000/api/user/register", 30 | "protocol": "http", 31 | "host": [ 32 | "localhost" 33 | ], 34 | "port": "8000", 35 | "path": [ 36 | "api", 37 | "user", 38 | "register" 39 | ] 40 | } 41 | }, 42 | "response": [] 43 | }, 44 | { 45 | "name": "User Login", 46 | "request": { 47 | "method": "POST", 48 | "header": [ 49 | { 50 | "key": "Accept", 51 | "value": "application/json", 52 | "type": "default" 53 | } 54 | ], 55 | "body": { 56 | "mode": "raw", 57 | "raw": "{\r\n \"email\":\"sonam@example.com\",\r\n \"password\":\"123456\"\r\n}", 58 | "options": { 59 | "raw": { 60 | "language": "json" 61 | } 62 | } 63 | }, 64 | "url": { 65 | "raw": "http://localhost:8000/api/user/login", 66 | "protocol": "http", 67 | "host": [ 68 | "localhost" 69 | ], 70 | "port": "8000", 71 | "path": [ 72 | "api", 73 | "user", 74 | "login" 75 | ] 76 | } 77 | }, 78 | "response": [] 79 | }, 80 | { 81 | "name": "Change User Password", 82 | "request": { 83 | "method": "POST", 84 | "header": [ 85 | { 86 | "key": "Accept", 87 | "value": "application/json", 88 | "type": "default" 89 | }, 90 | { 91 | "key": "Authorization", 92 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiI2MjE3NDg0MWNkYjYwNDBmMWU2NzI4M2YiLCJpYXQiOjE2NDU2OTMwMTIsImV4cCI6MTY0NjEyNTAxMn0.pHa_3QxsAy0hSFEfp4RYrmGecAARUwIIhB8K73SolBU", 93 | "type": "text" 94 | } 95 | ], 96 | "body": { 97 | "mode": "raw", 98 | "raw": "{\r\n \"password\":\"hello\",\r\n \"password_confirmation\":\"hello\"\r\n}", 99 | "options": { 100 | "raw": { 101 | "language": "json" 102 | } 103 | } 104 | }, 105 | "url": { 106 | "raw": "http://localhost:8000/api/user/changepassword", 107 | "protocol": "http", 108 | "host": [ 109 | "localhost" 110 | ], 111 | "port": "8000", 112 | "path": [ 113 | "api", 114 | "user", 115 | "changepassword" 116 | ] 117 | } 118 | }, 119 | "response": [] 120 | }, 121 | { 122 | "name": "Logged User Detail", 123 | "request": { 124 | "method": "GET", 125 | "header": [ 126 | { 127 | "key": "Accept", 128 | "value": "application/json", 129 | "type": "default" 130 | }, 131 | { 132 | "key": "Authorization", 133 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiI2MjE3NDg0MWNkYjYwNDBmMWU2NzI4M2YiLCJpYXQiOjE2NDU2OTMwMTIsImV4cCI6MTY0NjEyNTAxMn0.pHa_3QxsAy0hSFEfp4RYrmGecAARUwIIhB8K73SolBU", 134 | "type": "text" 135 | } 136 | ], 137 | "url": { 138 | "raw": "http://localhost:8000/api/user/loggeduser", 139 | "protocol": "http", 140 | "host": [ 141 | "localhost" 142 | ], 143 | "port": "8000", 144 | "path": [ 145 | "api", 146 | "user", 147 | "loggeduser" 148 | ] 149 | } 150 | }, 151 | "response": [] 152 | }, 153 | { 154 | "name": "Send Password Reset Email", 155 | "request": { 156 | "method": "POST", 157 | "header": [ 158 | { 159 | "key": "Accept", 160 | "value": "application/json", 161 | "type": "default" 162 | } 163 | ], 164 | "body": { 165 | "mode": "raw", 166 | "raw": "{\r\n \"email\":\"sonam@example.com\"\r\n}", 167 | "options": { 168 | "raw": { 169 | "language": "json" 170 | } 171 | } 172 | }, 173 | "url": { 174 | "raw": "http://localhost:8000/api/user/send-reset-password-email", 175 | "protocol": "http", 176 | "host": [ 177 | "localhost" 178 | ], 179 | "port": "8000", 180 | "path": [ 181 | "api", 182 | "user", 183 | "send-reset-password-email" 184 | ] 185 | } 186 | }, 187 | "response": [] 188 | }, 189 | { 190 | "name": "Password Reset", 191 | "request": { 192 | "method": "POST", 193 | "header": [ 194 | { 195 | "key": "Accept", 196 | "value": "application/json", 197 | "type": "default" 198 | } 199 | ], 200 | "body": { 201 | "mode": "raw", 202 | "raw": "{\r\n \"password\":\"12345678\",\r\n \"password_confirmation\":\"12345678\"\r\n}", 203 | "options": { 204 | "raw": { 205 | "language": "json" 206 | } 207 | } 208 | }, 209 | "url": { 210 | "raw": "http://localhost:8000/api/user/reset-password/62174841cdb6040f1e67283f/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiI2MjE3NDg0MWNkYjYwNDBmMWU2NzI4M2YiLCJpYXQiOjE2NDU2OTMxNzgsImV4cCI6MTY0NTY5NDA3OH0.XcuRC6ZY-eEk0e2Is4xwcmrKSyVugRaEmLCwyolre3E", 211 | "protocol": "http", 212 | "host": [ 213 | "localhost" 214 | ], 215 | "port": "8000", 216 | "path": [ 217 | "api", 218 | "user", 219 | "reset-password", 220 | "62174841cdb6040f1e67283f", 221 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiI2MjE3NDg0MWNkYjYwNDBmMWU2NzI4M2YiLCJpYXQiOjE2NDU2OTMxNzgsImV4cCI6MTY0NTY5NDA3OH0.XcuRC6ZY-eEk0e2Is4xwcmrKSyVugRaEmLCwyolre3E" 222 | ] 223 | } 224 | }, 225 | "response": [] 226 | } 227 | ] 228 | } -------------------------------------------------------------------------------- /controllers/userController.js: -------------------------------------------------------------------------------- 1 | import UserModel from '../models/User.js' 2 | import bcrypt from 'bcrypt' 3 | import jwt from 'jsonwebtoken' 4 | import transporter from '../config/emailConfig.js' 5 | 6 | class UserController { 7 | static userRegistration = async (req, res) => { 8 | const { name, email, password, password_confirmation, tc } = req.body 9 | const user = await UserModel.findOne({ email: email }) 10 | if (user) { 11 | res.send({ "status": "failed", "message": "Email already exists" }) 12 | } else { 13 | if (name && email && password && password_confirmation && tc) { 14 | if (password === password_confirmation) { 15 | try { 16 | const salt = await bcrypt.genSalt(10) 17 | const hashPassword = await bcrypt.hash(password, salt) 18 | const doc = new UserModel({ 19 | name: name, 20 | email: email, 21 | password: hashPassword, 22 | tc: tc 23 | }) 24 | await doc.save() 25 | const saved_user = await UserModel.findOne({ email: email }) 26 | // Generate JWT Token 27 | const token = jwt.sign({ userID: saved_user._id }, process.env.JWT_SECRET_KEY, { expiresIn: '5d' }) 28 | res.status(201).send({ "status": "success", "message": "Registration Success", "token": token }) 29 | } catch (error) { 30 | console.log(error) 31 | res.send({ "status": "failed", "message": "Unable to Register" }) 32 | } 33 | } else { 34 | res.send({ "status": "failed", "message": "Password and Confirm Password doesn't match" }) 35 | } 36 | } else { 37 | res.send({ "status": "failed", "message": "All fields are required" }) 38 | } 39 | } 40 | } 41 | 42 | static userLogin = async (req, res) => { 43 | try { 44 | const { email, password } = req.body 45 | if (email && password) { 46 | const user = await UserModel.findOne({ email: email }) 47 | if (user != null) { 48 | const isMatch = await bcrypt.compare(password, user.password) 49 | if ((user.email === email) && isMatch) { 50 | // Generate JWT Token 51 | const token = jwt.sign({ userID: user._id }, process.env.JWT_SECRET_KEY, { expiresIn: '5d' }) 52 | res.send({ "status": "success", "message": "Login Success", "token": token }) 53 | } else { 54 | res.send({ "status": "failed", "message": "Email or Password is not Valid" }) 55 | } 56 | } else { 57 | res.send({ "status": "failed", "message": "You are not a Registered User" }) 58 | } 59 | } else { 60 | res.send({ "status": "failed", "message": "All Fields are Required" }) 61 | } 62 | } catch (error) { 63 | console.log(error) 64 | res.send({ "status": "failed", "message": "Unable to Login" }) 65 | } 66 | } 67 | 68 | static changeUserPassword = async (req, res) => { 69 | const { password, password_confirmation } = req.body 70 | if (password && password_confirmation) { 71 | if (password !== password_confirmation) { 72 | res.send({ "status": "failed", "message": "New Password and Confirm New Password doesn't match" }) 73 | } else { 74 | const salt = await bcrypt.genSalt(10) 75 | const newHashPassword = await bcrypt.hash(password, salt) 76 | await UserModel.findByIdAndUpdate(req.user._id, { $set: { password: newHashPassword } }) 77 | res.send({ "status": "success", "message": "Password changed succesfully" }) 78 | } 79 | } else { 80 | res.send({ "status": "failed", "message": "All Fields are Required" }) 81 | } 82 | } 83 | 84 | static loggedUser = async (req, res) => { 85 | res.send({ "user": req.user }) 86 | } 87 | 88 | static sendUserPasswordResetEmail = async (req, res) => { 89 | const { email } = req.body 90 | if (email) { 91 | const user = await UserModel.findOne({ email: email }) 92 | if (user) { 93 | const secret = user._id + process.env.JWT_SECRET_KEY 94 | const token = jwt.sign({ userID: user._id }, secret, { expiresIn: '15m' }) 95 | const link = `http://127.0.0.1:3000/api/user/reset/${user._id}/${token}` 96 | console.log(link) 97 | // // Send Email 98 | // let info = await transporter.sendMail({ 99 | // from: process.env.EMAIL_FROM, 100 | // to: user.email, 101 | // subject: "GeekShop - Password Reset Link", 102 | // html: `Click Here to Reset Your Password` 103 | // }) 104 | res.send({ "status": "success", "message": "Password Reset Email Sent... Please Check Your Email" }) 105 | } else { 106 | res.send({ "status": "failed", "message": "Email doesn't exists" }) 107 | } 108 | } else { 109 | res.send({ "status": "failed", "message": "Email Field is Required" }) 110 | } 111 | } 112 | 113 | static userPasswordReset = async (req, res) => { 114 | const { password, password_confirmation } = req.body 115 | const { id, token } = req.params 116 | const user = await UserModel.findById(id) 117 | const new_secret = user._id + process.env.JWT_SECRET_KEY 118 | try { 119 | jwt.verify(token, new_secret) 120 | if (password && password_confirmation) { 121 | if (password !== password_confirmation) { 122 | res.send({ "status": "failed", "message": "New Password and Confirm New Password doesn't match" }) 123 | } else { 124 | const salt = await bcrypt.genSalt(10) 125 | const newHashPassword = await bcrypt.hash(password, salt) 126 | await UserModel.findByIdAndUpdate(user._id, { $set: { password: newHashPassword } }) 127 | res.send({ "status": "success", "message": "Password Reset Successfully" }) 128 | } 129 | } else { 130 | res.send({ "status": "failed", "message": "All Fields are Required" }) 131 | } 132 | } catch (error) { 133 | console.log(error) 134 | res.send({ "status": "failed", "message": "Invalid Token" }) 135 | } 136 | } 137 | } 138 | 139 | export default UserController --------------------------------------------------------------------------------