Last active
October 23, 2019 03:47
-
-
Save pjchender/bba7bb7bc819e6997d8a17b2a014c68f to your computer and use it in GitHub Desktop.
Learn to Use Passport(Passport 學習筆記)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 匯入需要的模組 | |
const express = require('express') | |
const bodyParser = require('body-parser') | |
const cookieParser = require('cookie-parser') | |
const path = require('path') | |
const logger = require('morgan') | |
const mongoose = require('mongoose') | |
const session = require('express-session') | |
const passport = require('./middleware/passport') | |
const MongoStore = require('connect-mongo')(session) // 直接執行並將 session 存進去,logout 後會自動刪除該 document | |
const dbconfig = require('./db') | |
// 和 mongoDB 連線 | |
mongoose.connect(dbconfig.connection) // 等同於,mongoose.connect('mongodb://localhost:27017/bookworm') | |
const db = mongoose.connection | |
db.on('error', console.error.bind(console, 'connection error')) // mongo error handler | |
// 載入 express | |
const app = express() | |
// 設定 view engine 和模版路徑 | |
app.set('view engine', 'pug') | |
app.set('views', path.join('./views')) | |
// middleware | |
app.use(logger('dev')) | |
app.use(bodyParser.json()) | |
app.use(bodyParser.urlencoded({ extended: true })) | |
app.use(cookieParser()) | |
app.use(express.static(path.join('./public'))) // 讀取 ./public 中的靜態檔案 | |
// 使用 session 來追蹤使用者 | |
app.use(session({ | |
secret: 'I love NodeJS', // secret: 必要欄位,用來註冊 session ID cookie 的字串。如此將增加安全性,避免他人在瀏覽器中偽造 cookie。 | |
resave: false, // resave: 不論是否 request 的過程中有無變更都重新將 session 儲存在 session store。 | |
saveUninitialized: false, // saveUninitialized: 將 uninitialized session(新的、未被變更過的) 儲存在 session store 中。 | |
store: new MongoStore({ | |
mongooseConnection: db | |
}) | |
})) | |
app.use(passport.initialize()) | |
app.use(passport.session()) | |
// 讓 userID 可以在 template 中被存取,名稱為 currentUser | |
app.use(function (req, res, next) { | |
res.locals.currentUser = req.session.userId // res.locals 屬性在所有 view 中都可以存取到 | |
next() // 執行下一個 middleware | |
}) | |
// 載入路由檔 | |
const index = require('./routes/index') | |
app.use('/', index) | |
// catch 404 and forward to error handler | |
app.use(function (req, res, next) { | |
var err = new Error('File Not Found') | |
err.status = 404 | |
next(err) | |
}) | |
// error handler | |
// define as the last app.use callback | |
app.use(function (err, req, res, next) { | |
res.status(err.status || 500) | |
res.render('error', { | |
message: err.message, | |
error: {} | |
}) | |
}) | |
// listen on port 3000 | |
app.listen(3000, function () { | |
console.log('Express app listening on port 3000') | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./routes/index | |
const express = require('express') | |
const router = express.Router() | |
const User = require('../models/user') | |
const mid = require('../middleware') // 將自己寫的 middleware 載入 | |
const passport = require('../middleware/passport') | |
// POST /login | |
router.post('/login', function (req, res, next) { | |
passport.authenticate('login', function (err, user, info){ | |
if (err) return next(err) | |
if (!user) { | |
err = new Error('User not found') | |
res.status(409) | |
return next(err) | |
} | |
req.login(user, function (err) { | |
if (err) return next(err) | |
req.session.userId = user._id | |
return res.redirect('/profile') | |
}) | |
})(req, res, next) | |
}) | |
// POST /register | |
router.post('/register', function (req, res, next) { | |
passport.authenticate('signup', function (err, user, info) { | |
if (err) { | |
err = new Error('Singup Error') | |
err.status = 409 | |
return next(err) | |
} | |
if (!user) { | |
err = new Error(info) | |
err.status = 409 | |
return next(err) | |
} | |
req.login(user, function (err) { | |
if (err) return next(err) | |
console.log('User account created successfully') | |
req.session.userId = user._id | |
return res.redirect('/profile') | |
}) | |
})(req, res, next) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const passport = require('passport') | |
const bcrypt = require('bcrypt') // hashing module | |
const LocalStrategy = require('passport-local') | |
const JwtStrategy = require('passport-jwt').Strategy | |
const ExtractJwt = require('passport-jwt').ExtractJwt | |
const User = require('../models/user') | |
const jwtConfig = require('../config/jwt') | |
/** | |
* passport.use('驗證策略名稱', '想建立的策略類型') | |
* passReqToCallback: 讓我們在後面的 callback 中可以使用 req 參數 | |
*/ | |
// Passport Initialization | |
passport.serializeUser(function (user, done) { | |
done(null, user._id) | |
}) | |
passport.deserializeUser(function (id, done) { | |
User.findById(id, function (err, user) { | |
done(err, user) | |
}) | |
}) | |
let jwtStrategy = new JwtStrategy({ | |
secreteOrKey: jwtConfig.secret, | |
jwtFromRequest: ExtractJwt.fromExtractors([ | |
ExtractJwt.versionOneCompatibility({authScheme: 'Bearer'}), | |
ExtractJwt.fromAuthHeader() | |
]) | |
}, function (payload, done) { | |
User.findById(payload.sub, function (err, user) { | |
if (err) return done(err) | |
if (!user) return done(null, false, {message: 'Wrong JWT Token'}) | |
if (payload.aud !== user.email) return done(null, false, {message: 'Wrong JWT Token'}) | |
const exp = payload.exp | |
const nbf = payload.nbf | |
const curr = ~~(new Date().getTime() / 1000) | |
if (curr > exp || curr < nbf) { | |
return done(null, false, 'Token Expired') | |
} | |
return done(null, user) | |
}) | |
}) | |
let loginStrategy = new LocalStrategy({ | |
usernameField: 'email', | |
passReqToCallback: true | |
}, function (req, email, password, done) { | |
User.findOne({ email: email }, function (err, user) { | |
if (err) { | |
return done(err) | |
} | |
if (!user) { | |
return done(null, false, 'Username is not exists') | |
} | |
let isValidPassword = function (user, password) { | |
return bcrypt.compareSync(password, user.password) | |
} | |
if (!isValidPassword(user, password)) { | |
return done(null, false, 'Invalid Password') | |
} | |
return done(null, user) | |
}) | |
}) | |
let signupStrategy = new LocalStrategy({ | |
usernameField: 'email', | |
passReqToCallback: true | |
}, function (req, email, password, done) { | |
if (password !== req.body.confirmPassword) { | |
return done(new Error('Confirmation is not match with password')) | |
} | |
// if (password.length < 8) { | |
// return done(new Error('Password must has 8 characters at least')) | |
// } | |
const findOrCreateUser = function () { | |
User.findOne({ email: email }, function (err, user) { | |
if (err) { | |
return done(err) | |
} | |
if (user) { | |
return done(null, false, 'Username Already Exists') | |
} else { | |
let newUser = new User() | |
newUser.email = email | |
newUser.password = bcrypt.hashSync(password, 10) | |
newUser.name = req.body.name | |
newUser.favoriteBook = req.body.favoriteBook | |
newUser.save(function (err, user) { | |
if (err) { | |
throw err | |
} | |
return done(null, user) | |
}) | |
} | |
}) | |
} | |
process.nextTick(findOrCreateUser) | |
}) | |
passport.use('jwt', jwtStrategy) | |
passport.use('login', loginStrategy) | |
passport.use('signup', signupStrategy) | |
module.exports = passport |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./models/user | |
const mongoose = require('mongoose') // ODM for Mongo | |
const UserSchema = new mongoose.Schema({ | |
email: { | |
type: String, | |
required: true, | |
trim: true, | |
unique: true | |
}, | |
name: { | |
type: String, | |
required: true, | |
trim: true | |
}, | |
favoriteBook: { | |
type: String, | |
required: true, | |
trim: true | |
}, | |
password: { | |
type: String, | |
required: true | |
} | |
}) | |
var User = mongoose.model('User', UserSchema) | |
module.exports = User |
[app.js]
- app.js
[middleware]
- 使用 passport 的時候要記得在 middleware 中
app.use(passport.initialize())
- 如果有使用 session(建議但非必須),則要使用
app.use(passport.session())
,記得要放在express.session()
的後面
app.configure(function () {
app.use(express.static('public'))
app.use(express.cookieParser())
app.use(express.bodyParser())
app.use(express.session({ secret: 'keyboard cat' }))
app.use(passport.initialize())
app.use(passport.session())
app.use(app.router)
})
推薦閱讀
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
passport.js
./middleware/passport.js
中[Session]
req.user
中[LocalStrategy]
passport.use(<strategy>)
來設定 strategyverify callback
,當 passport authenticate 接收到一個 request 時,它會去解析 request 中和認證有關的訊息(credentials),接著它會把這些 credentials 作為代入 verify callback 的參數:-- 如果 credentials 有效(valid),則會呼叫
return done(null, user)
-- 如果 credentials 無效,則會呼叫並可顯示錯誤訊息
return done(null, false, {message: 'Wrong Password'})
-- 如果在過程中發生例外,例如連不上 db ,則會呼叫
return done(err)
LocalStrategy
會以username
和password
當作驗證的欄位,如果有變更的話,可以透過usernameField
和passwordField
來改變[JwtStrategy]
new JwtStrategy(options, verify)
--
secretOrKey
: 必填欄位--
jwtFromRequest
: 用來代入驗證的函式--
issuer
: 可以驗證 iss--
audience
: 可以驗證 aud--
algorithms
: 列出允許的 algorithm--
ignoreExpiration
: 如果設成 true 則不驗證過期日--
passReqToCallback
: 如果設成 true 則可以在 verify 的 callback 中使用 req,verify(request, jwt_payload, done_callback)
verify(jwt_payload, done)
-- payload 是解碼後的 JWT payload
-- done 是一個 callback<error, user, infor>
--
fromHeader(header_name)
: 從指定的 http header name 中找 JWT--
fromBodyField(field_name)
: 從 body 的欄位中找 JWT--
fromUrlQueryParameter(param_name)
: 從 URL 的 query parameter 中找 JWT--
fromAuthHeaderWithScheme(auth_scheme)
: 從 authorization header 中找 JWT--
fromAuthHeader()
: 以 scheme 'JWT' 尋找 authorization header(HTTP Header 的寫法要是{Authorization: JWT xxx.yyy.zzz}
)--
fromExtractors([array of extractor functions])
可以用陣列的方式列出所有上述想要使用的方法