OAuth

使用 Passport 認證策略中的,facebook 登入認證,透過 OAuth 2.0 機制,取得使用者同意後,使我們可以快速登入。

OAuth

處理流程中有四個角色

  • 資源擁有者: 帳號資料使用者。(User)
  • 授權伺服器: 取得使用者許可後簽發 access token 的第三方網站(facebook}
  • 資源伺服器: 保管資源擁有者資料的伺服器(facebook)
  • 客戶端: 使用者正在使用的第三方應用程式

英文分別為: Resource ownerAuthorization serverResource ServerClient

實作步驟

  1. facebook for developers 上設定應用程式
  2. 安裝 Passport-Facebook
  3. 新增 auths 路由
  4. app.js 載入路由
  5. FacebookStrategy

進入facebook for developers設置應用程式,選擇整合 facebook 登入,取得應用程式編號、應用程式密鑰。

安裝 Passport-Facebook

npm install passport-facebook

新增路由 auths.js 載入 app.js

參考 Passport

app.get('/auth/facebook',
passport.authenticate('facebook'));

app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/login' }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/');
});

新增路由 routes/auths.js

const 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.js

app.use('/users', require('./routes/user'))     
app.use('/auth', require('./routes/auths')) //加入

FacebookStrategy

在 passport middleware 加入 facebookStrategy

官方文件

審核創建新使用者
config/passport.js

const 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) 管理資訊,藉由讀取環境變數去知道作業系統的資訊。

實作步驟

  1. 安裝 dotenv
  2. 隱藏資訊設定為變數,寫進env
  3. config/passport.js 用相同變數取代敏感資訊。
  4. 在 app.js 增加應用程式執行模式邏輯
  5. .env加入 .gitignore
  6. 檢查登入

安裝 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.js

if (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 = {
authenticated: (req, res, next) => {
if (req.isAuthenticated()) {
return next
}
req.flash('warning_msg', '請先登入')
res.redirect('/users/login')
}
}

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))
})
)
}
})
}
})