├── .gitignore ├── README.md ├── config ├── index.js └── prod.js ├── controller └── AuthController.js ├── index.js ├── models └── User.js ├── package-lock.json ├── package.json ├── routes └── Auth.js └── services └── passport.js /.gitignore: -------------------------------------------------------------------------------- 1 | config/dev.js 2 | node_modules 3 | .next -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Authentication with Next.js 2 | 3 | > This is the project for the blog post: Authentication in Next.js with SWR 4 | 5 | ## Installing Packages 6 | 7 | In order to install the packages, you need to write the following command:
8 | `npm install `. 9 | 10 | ## Adding configuration 11 | 12 | The config directory should contain _dev.js_ file while working in the development build. So, create a file called _dev.js_. Then, you need to export `MONGO_URI` and `SECRET`. The file should look something like this: 13 | 14 | ``` 15 | 16 | module.exports = { 17 | MONGO_URI: "", // MongoDB's URI goes here 18 | SECRET: "", // SECRET key goes here 19 | }; 20 | 21 | ``` 22 | 23 | ## Final Project 24 | 25 | To take a look at the final project, you ned to change the git branch to final project. You can just write `git checkout final-project`. 26 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === "production") { 2 | module.exports = require("./prod"); 3 | } else { 4 | module.exports = require("./dev"); 5 | } 6 | -------------------------------------------------------------------------------- /config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | MONGO_URI: process.env.MONGO_URI, 3 | SECRET: process.env.SECRET, 4 | }; 5 | -------------------------------------------------------------------------------- /controller/AuthController.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User"); 2 | 3 | const login = (req, res) => { 4 | return res.status(200).json({ msg: "user sucessfully logged in" }); 5 | }; 6 | 7 | const signup = async (req, res) => { 8 | const { email, name, password } = req.body; 9 | try { 10 | let user = await User.findOne({ email }); 11 | if (!user) { 12 | let newUser = new User({ name, email, password }); 13 | await newUser.save(); 14 | return res.status(200).json({ msg: "user successfully created" }); 15 | } 16 | return res 17 | .status(422) 18 | .json({ errors: ["the user with this email already exists"] }); 19 | } catch (error) { 20 | console.error(error); 21 | 22 | return res.status(500).json({ errors: ["some error occured"] }); 23 | } 24 | }; 25 | 26 | const logout = (req, res) => { 27 | req.logout(); 28 | res.status(200).json({ msg: "logged out" }); 29 | }; 30 | 31 | const me = (req, res) => { 32 | if (!req.user) 33 | return res.status(403).json({ errors: ["login to get the info"] }); 34 | 35 | return res.status(200).json({ user: req.user }); 36 | }; 37 | 38 | module.exports = { 39 | login, 40 | signup, 41 | logout, 42 | me, 43 | }; 44 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const mongoose = require("mongoose"); 3 | const session = require("express-session"); 4 | const MongoStore = require("connect-mongo")(session); 5 | const next = require("next"); 6 | 7 | const { MONGO_URI, SECRET } = require("./config"); 8 | 9 | const passport = require("./services/passport"); 10 | const AuthRoute = require("./routes/Auth"); 11 | 12 | const app = express(); 13 | 14 | const dev = process.env.NODE_ENV !== "production"; 15 | const nextApp = next({ dev }); 16 | const handle = nextApp.getRequestHandler(); 17 | const PORT = process.env.PORT || 5000; 18 | 19 | nextApp.prepare().then(() => { 20 | app.use(express.json()); 21 | 22 | app.use( 23 | session({ 24 | secret: SECRET, 25 | resave: false, 26 | saveUninitialized: false, 27 | cookie: { secure: false }, 28 | store: new MongoStore({ 29 | mongooseConnection: mongoose.connection, 30 | }), 31 | }) 32 | ); 33 | 34 | app.use(passport.initialize()); 35 | app.use(passport.session()); 36 | 37 | app.use("/api/auth", AuthRoute); 38 | 39 | app.get("*", (req, res) => { 40 | handle(req, res); 41 | }); 42 | 43 | mongoose 44 | .connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }) 45 | .then(() => console.log("conected to mongo database")) 46 | .catch((e) => console.error(e)); 47 | 48 | app.listen(PORT, () => console.log(`listening on PORT ${PORT}`)); 49 | }); 50 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const bcrypt = require("bcrypt"); 3 | 4 | const { Schema } = mongoose; 5 | 6 | const UserSchema = new Schema({ 7 | name: String, 8 | email: String, 9 | password: String, 10 | }); 11 | 12 | UserSchema.pre("save", async function (next) { 13 | const user = this; 14 | 15 | try { 16 | if (!user.isModified("password")) next(); 17 | 18 | let hash = await bcrypt.hash(user.password, 13); 19 | user.password = hash; 20 | next(); 21 | } catch (error) { 22 | console.error(error); 23 | next(error); 24 | } 25 | }); 26 | 27 | UserSchema.methods.comparePassword = async function (password) { 28 | try { 29 | let result = await bcrypt.compare(password, this.password); 30 | 31 | return result; 32 | } catch (error) { 33 | console.error(error); 34 | return false; 35 | } 36 | }; 37 | 38 | module.exports = mongoose.model("user", UserSchema); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authentication", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon index.js --ignore ./component --ignore ./pages", 8 | "build": "next build", 9 | "start": "next start" 10 | }, 11 | "keywords": [], 12 | "author": "aa1aac", 13 | "license": "ISC", 14 | "dependencies": { 15 | "axios": "^0.19.2", 16 | "bcrypt": "^5.0.0", 17 | "bootstrap": "^4.5.2", 18 | "connect-mongo": "^3.2.0", 19 | "express": "^4.17.1", 20 | "express-session": "^1.17.1", 21 | "express-validator": "^6.6.1", 22 | "mongoose": "^5.10.0", 23 | "next": "^9.5.2", 24 | "passport": "^0.4.1", 25 | "passport-local": "^1.0.0", 26 | "react": "^16.13.1", 27 | "react-bootstrap": "^1.3.0", 28 | "react-dom": "^16.13.1", 29 | "swr": "^0.3.0" 30 | }, 31 | "devDependencies": { 32 | "nodemon": "^2.0.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /routes/Auth.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const passport = require("passport"); 3 | const { check, validationResult } = require("express-validator"); 4 | 5 | const { login, logout, signup, me } = require("../controller/AuthController"); 6 | 7 | const router = express.Router(); 8 | 9 | // /api/auth/signup 10 | router.post( 11 | "/signup", 12 | [ 13 | check("name") 14 | .isLength({ min: 3 }) 15 | .withMessage("the name must have minimum length of 3") 16 | .trim(), 17 | 18 | check("email") 19 | .isEmail() 20 | .withMessage("invalid email address") 21 | .normalizeEmail(), 22 | 23 | check("password") 24 | .isLength({ min: 8, max: 15 }) 25 | .withMessage("your password should have min and max length between 8-15") 26 | .matches(/\d/) 27 | .withMessage("your password should have at least one number") 28 | .matches(/[!@#$%^&*(),.?":{}|<>]/) 29 | .withMessage("your password should have at least one sepcial character"), 30 | 31 | check("confirmPassword").custom((value, { req }) => { 32 | if (value !== req.body.password) { 33 | throw new Error("confirm password does not match"); 34 | } 35 | return true; 36 | }), 37 | ], 38 | (req, res, next) => { 39 | const error = validationResult(req).formatWith(({ msg }) => msg); 40 | 41 | const hasError = !error.isEmpty(); 42 | 43 | if (hasError) { 44 | res.status(422).json({ error: error.array() }); 45 | } else { 46 | next(); 47 | } 48 | }, 49 | signup 50 | ); 51 | 52 | // /api/auth/login 53 | router.post( 54 | "/login", 55 | passport.authenticate("local", { 56 | failureMessage: "Invalid username or password", 57 | }), 58 | login 59 | ); 60 | 61 | // /api/auth/logout 62 | router.get("/logout", logout); 63 | 64 | // /api/auth/me 65 | router.get("/me", me); 66 | 67 | module.exports = router; 68 | -------------------------------------------------------------------------------- /services/passport.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | const LocalStrategy = require("passport-local"); 3 | 4 | 5 | const User = require("../models/User"); 6 | 7 | passport.serializeUser((user, done) => { 8 | done(null, user._id); 9 | }); 10 | 11 | passport.deserializeUser(async (id, done) => { 12 | try { 13 | let user = await User.findById(id, "name email"); 14 | 15 | if (!user) return done(new Error("user not found")); 16 | 17 | return done(null, user); 18 | } catch (error) { 19 | console.error(error); 20 | done(error); 21 | } 22 | }); 23 | 24 | passport.use( 25 | new LocalStrategy( 26 | { usernameField: "email" }, 27 | async (email, password, done) => { 28 | try { 29 | const user = await User.findOne({ email }); 30 | 31 | if (!user) return done(null, false); 32 | 33 | const passwordMatch = await user.comparePassword(password); 34 | 35 | if (!passwordMatch) return done(null, false); 36 | 37 | return done(null, user); 38 | } catch (err) { 39 | return done(err); 40 | } 41 | } 42 | ) 43 | ); 44 | 45 | module.exports = passport; 46 | --------------------------------------------------------------------------------