「使用passport middleware實作登入系統」相關筆記

總結

本次練習包含以下幾個主要功能:

環境

passport: 0.4.1
passport-local: 1.0.0
express: 4.17.1
express-session: 1.17.2
connect-flash: 0.1.1
os: Windows_NT 10.0.18363 win32 x64

筆記

app.js

const express = require('express')
const app = express()
const port = 5000
// DB
require('./config/mongoose')
// form handling
const bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
// session setting for passport
const session = require('express-session')
app.use(session({
secret: 'keyboard cat',
resave: true,
saveUninitialized: true
}))
// display message after redirect by session and flash
const flash = require('connect-flash')
app.use(flash())
app.use((req, res, next) => {
res.locals.successMessage = req.flash('successMessage')
res.locals.errorMessage = req.flash('errorMessage')
res.locals.error = req.flash('error')
next()
})
// passport
const passport = require('passport')
const loginVerify = require('./config/passport')
loginVerify(passport)
app.use(passport.initialize())
app.use(passport.session())
// rendering template
const expressHandlebars = require('express-handlebars')
app.engine('handlebars', expressHandlebars({ defaultLayout: 'main' }))
app.set('view engine', 'handlebars')
// routes
const routes = require('./routes')
app.use(routes)
// scripts, styles
app.use(express.static('public'))
app.listen(port, () => {
console.log(`Express is listening on localhost:${port}`)
})
view raw passport.app.js hosted with ❤ by GitHub

config/passport.js

const LocalStrategy = require('passport-local').Strategy
const bcrypt = require('bcrypt')
const User = require('../models/user')
function loginVerify (passport) {
passport.use(new LocalStrategy({ usernameField: 'email' }, async (email, password, done) => {
const user = await User.findOne({ email })
// check if user exist
if (!user) return done(null, false, { message: 'User not exist' })
// check password
const compareResult = await bcrypt.compare(password, user.password)
return compareResult ? done(null, user) : done(null, false, { message: 'Password incorrect' })
})
)
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser((id, done) => {
User.findById(id, (error, user) => {
done(error, user)
})
})
}
module.exports = loginVerify

config/auto.js

function isLoggedIn (req, res, next) {
if (req.isAuthenticated()) {
return next()
}
req.flash('errorMessage', 'Please log in to view this page.')
res.redirect('/user/login')
}
function notLoggedIn (req, res, next) {
if (!req.isAuthenticated()) {
return next()
}
res.redirect('/dashboard')
}
module.exports = { isLoggedIn, notLoggedIn }

routes/modules

const express = require('express')
const router = express.Router()
// check if user has logged in
const { isLoggedIn, notLoggedIn } = require('../../config/auth')
router.get('/', notLoggedIn, (req, res) => {
res.render('index')
})
router.get('/dashboard', isLoggedIn, (req, res) => {
res.render('dashboard', { user: req.user.username })
})
module.exports = router
const express = require('express')
const router = express.Router()
// DB
const User = require('../../models/user')
// password hash
const bcrypt = require('bcrypt')
const saltRounds = 10
// passport
const passport = require('passport')
// only direct not logged in user to login or register endpoint
const { notLoggedIn } = require('../../config/auth')
router.get('/login', notLoggedIn, (req, res) => {
res.render('login')
})
router.post('/login', passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/user/login',
failureFlash: true
}))
router.get('/register', notLoggedIn, (req, res) => {
res.render('register')
})
router.post('/register', async (req, res) => {
const { username, email, password } = req.body
const registerErrors = []
if (!username || !email || !password) registerErrors.push({ message: 'Please fill in all fields' })
if (email) {
const find = await User.findOne({ email })
if (find) registerErrors.push({ message: 'User already exist' })
}
if (registerErrors.length) {
res.render('register', { registerErrors, username, email, password })
return
}
const hashPassword = await bcrypt.hash(password, saltRounds)
const newUser = new User({
username,
email,
password: hashPassword
})
await newUser.save()
req.flash('successMessage', 'You are now register and can log in.')
res.redirect('/user/login')
})
router.get('/logout', (req, res) => {
req.logout()
req.flash('successMessage', 'You have logged out.')
res.redirect('/user/login')
})
module.exports = router

views/partials

{{#if registerErrors}}
{{#each registerErrors}}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
{{this.message}}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{/each}}
{{/if}}
{{#if successMessage}}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{successMessage}}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{/if}}
{{#if errorMessage}}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
{{errorMessage}}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{/if}}
{{#if error}}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
{{error}}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{/if}}
<h1>Login</h1>
{{> messages }}
<form action="/user/login" method="POST" autocomplete="off">
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" placeholder="email">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" placeholder="password">
</div>
<button class="btn btn-primary" type="submit">Login</button>
<a href="/user/register" class="mx-3">Register</a>
<a href="/">Homepage</a>
</form>
<h1>Register</h1>
{{> messages }}
<form action="/user/register" method="POST" autocomplete="off">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" placeholder="username" value="{{ username }}">
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" placeholder="email" value="{{ email }}">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" placeholder="password">
</div>
<button class="btn btn-primary" type="submit">Register</button>
<a href="/user/login" class="mx-3">Login</a>
<a href="/">Homepage</a>
</form>
/views
  /layouts
    main.handlebars
  /partials
    messages.handlebars
  dashboard.handlebars
  index.handlebars
  login.handlebars
  register.handlebars

參考文件