使用 Passport 認證策略中的,facebook 登入認證,透過 OAuth 2.0 機制,取得使用者同意後,使我們可以快速登入。
OAuth
處理流程中有四個角色
- 資源擁有者: 帳號資料使用者。(User)
- 授權伺服器: 取得使用者許可後簽發 access token 的第三方網站(facebook}
- 資源伺服器: 保管資源擁有者資料的伺服器(facebook)
- 客戶端: 使用者正在使用的第三方應用程式
英文分別為: Resource owner
、Authorization server
、Resource Server
、Client
實作步驟
- facebook for developers 上設定應用程式
- 安裝 Passport-Facebook
- 新增 auths 路由
- app.js 載入路由
- FacebookStrategy
進入facebook for developers設置應用程式,選擇整合 facebook 登入
,取得應用程式編號、應用程式密鑰。
安裝 Passport-Facebook
npm install passport-facebook |
新增路由 auths.js 載入 app.js
參考 Passport
app.get('/auth/facebook', |
新增路由 routes/auths.jsconst express = require('express')
const router = express.Router()
const passport = require('passport')
router.get('/facebook',
passport.authenticate('facebook', {scope: ['email', 'public_profile']})
)
router.get('/facebook/callback',
passport.authenticate('facebook', {
successRedirect: '/',
failureRedirect: '/users/login',
})
)
module.exports = router
app.js 載入 routes/auths.jsapp.use('/users', require('./routes/user'))
app.use('/auth', require('./routes/auths')) //加入
FacebookStrategy
在 passport middleware 加入 facebookStrategy
審核創建新使用者
config/passport.jsconst LocalStrategy = require('passport-local').Strategy // passport-local
const FacebookStrategy = require('passport-facebook').Strategy //passport-facebook
const bcrypt = require('bcryptjs')
const User = require('../models./user')
module.exports = passport => {
passport.use(
new LocalStrategy({usernameField: 'email'}, (email, password, done)=> {
//...
})
)
passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET,
callbackURL: process.env.FACEBOOK_CALLBACK,
profileFields: ['email', 'displayName']
},(accessToken, refreshToken, profile, done) => {
User.findOne({
email: profile._json.email
}).then(user => {
if (!user) {
const randomPassword = Math.random(36).slice(-8)
bcrypt.genSalt(10, (err, salt) =>
bcrypt.hash(randomPassword, salt, (err, hash) =>{
const newUser = User({
name: profile._json.name,
email: profile._json.email,
password: hash
})
newUser.save().then(user => {
return done(null, user)
}).catch(err => {
console.log(err)
})
})
)
} else {
return done(null, user)
}
})
})
)
}
隱藏敏感資訊
將應用程式的編碼以及程式密鑰等敏感資訊,隱藏起來
使用 環境變數(environment variable)
管理資訊,藉由讀取環境變數去知道作業系統的資訊。
實作步驟
- 安裝 dotenv
- 隱藏資訊設定為變數,寫進env
- config/passport.js 用相同變數取代敏感資訊。
- 在 app.js 增加應用程式執行模式邏輯
- .env加入 .gitignore
- 檢查登入
安裝 dotenv
npm install dotenv |
修改 config/passport.js 及 新增.env
config/passport.js 已在上方做修改
將資訊放入.env,xxxxxxxx 找自己應用程式編號及密鑰// .env
FACEBOOK_ID=xxxxxxxx
FACEBOOK_SECRET=xxxxxxxx
FACEBOOK_CALLBACK=http://localhost:3000/auth/facebook/callback
判別環境
如果 程式不是在線上正式執行就透過 dotnev 讀取檔案
app.jsif (precess.env.NODE_ENV !== 'porduction'){ // 如果不是產品模式
require('dotenv').config() // dotenv 執行
}
最後將 .env 加入 .gitignore
新增 FB 按鈕
修改 login 樣板,加入連結。<a href="/auth/facebook">FB login</a>
partials 樣板
使用 connect-flash,使用 res 的 res.locals 將資訊傳到 view
在 app.js 載入const flash =require('connect-flash')
app.use(flash())
app.use((req, res, next) => {
res.locals.user = req.user
res.locals.isAuthenticated = req.isAuthenticated()
res.locals.success_msg = req.flash('success_msg')
res.locals.warning_msg = req.flash('warning_msg')
next()
})
使用handlebars {{> myPartial }} 將顯示加入。
使用者權限訊息加入 auth middleware 中
config/auth.js
module.export = { |
user.js// 檢查註冊
router.post('register', (req, res) => {
const {name, email, password, password2} = req.body
let errors = []
if (!name || !email || !password || !password2) {
errors.push({ message: '所有欄位都是必填' })
}
if (password !== password2) {
errors.push({ message: '密碼輸入錯誤' })
}
if (errors.length > 0) {
res.render('register', {
errors,
name,
email,
password,
password2
})
} else {
User.findOne({ email: email }).then(user => {
if (user) {
// 加入訊息提示
errors.push({ message: '這個 Email 已經註冊過了' })
res.render('register', {
errors,
name,
email,
password,
password2
})
} else {
const newUser = new User({
name,
email,
password
})
bcrypt.genSalt(10, (err, salt) =>
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err
newUser.password = hash
newUser
.save()
.then(user => {
res.redirect('/')
})
.catch(err => console.log(err))
})
)
}
})
}
})