เข้ารหัส Password ด้วย ฺbcrypt ใน Express สำหรับ ระบบสมาชิก

เขียนเมื่อ 4 ปีก่อน โดย Ninenik Narkdee
nodejs bcrypt session expressjs

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ nodejs bcrypt session expressjs

ดูแล้ว 7,649 ครั้ง


เนื้อหานี้ เป็นตอนที่ 3 แล้วเกี่ยวกับระบบสมาชิก โดยเราจะมาต่อ
ในส่วนของการบันทึกข้อมูลลงฐานข้อมูล MongoDB ในขั้นตอน
การสมัครสมาชิก การค้นหาและตรวจสอบข้อมูลกับฐานข้อมูล MongoDB
ในขั้นตอนการตรวจสอบอีเมลและการล็อกอินเข้าสู่ระบบสมาชิก
การแสดงข้อมูลใน MongoDB ในขั้นตอนการแสดงข้อมูลหน้าสมาชิก เป็นต้น 
    สำหรับขั้นตอนในการใช้งาน bcrypt ในการเข้ารหัสข้อมูลก่อนทำการบันทึกลงฐานข้อมูล
ซี่งในที่นี้เราจะทำการเข้ารหัส password ก่อนที่จะบันทึก และมีการตรวจสอบรหัสผ่าน ที่บันทึกไว้
ในหน้าล็อกอิน อย่างไรก็ตามในการใช้งาน bcrypt module ให้เราทำการติดตั้งก่อนใช้งานดังนี้
 
 

การติดตั้ง Bcrypt Module

    โดยให้ทำการติดตั้ง bcrypt ด้วยคำสั่ง
 
npm install bcrypt --save
 
 

การสร้าง Models สำหรับระบบสมาชิก

    ในระบบสมาชิก จะมีขั้นตอนการทำงานต่างๆ เช่น การเพิ่มข้อมูล กรณีสมัครสมาชิกใหม่   การค้นหาและตรวจสอบ
ข้อมูลกรณีล็อกอิน การแสดงข้อมูลกรณีล็อกอินสำเร็จและอยู่ที่หน้าสมาชิก เหล่านี้เป็นกระบวนการที่จะเกิดขึ้น ซึ่งเราจะ
สร้างเป็นชุด models ฟังก์ชั่นสำหรับเรียกใช้งาน
    แต่ก่อนอื่นสิ่งที่เราต้องมี กรณีใช้งานร่วมกับฐานข้อมูล MongoDB คือ Document Collection ที่เกี่ยวข้อง ในที่เราจะ
สร้างเพิ่มมา 2 collection คือ "lastid" กับ "users" 
    ทบทวนเกี่ยวกับการใช้งาน MongoDB เบื้องต้นได้ที่ http://niik.in/917
 
    สำหรับ "users" collection เราจะใช้สำหรับเก็บข้อมูลของสมาชิก ส่วน "lastid" เราจะใช้สำหรับเก็บข้อมูล id ล่าสุดของ
ข้อมูลที่เราต้องการประยุกต์ใช้งานในรูปแบบ auto increment โดยใน "lastid" ให้เราเพิ่มข้อมูลเข้าไปเบื้องต้นดังนี้
 
 

 
 
    เราจะกำหนดให้ user_id สำหรับเป็นค่าไว้เก็บ auto increment ของ user โดยจะเริ่มต้นที่ 1000 นั่นคือ ก่อนที่จะมีการ
เพิ่มข้อมูลเข้าไปในระบบ จะมาอ่านค่า user_id ใน "lastid" จากนั้นก็บวกค่าเพิ่มไป 1 แล้วนำไปบันทึกเป็นค่า primary key
ซึ่งเราจะใช้ฟิลด์ "_id" เก็บค่านี้ใน "users" collection 
    หลังจากเตรียมในส่วนของฐานข้อมูล MongoDB เรียบร้อยแล้ว ให้เราสร้างไฟล์ models ชื่อ users.js ไว้ในโฟลเดอร์
models จะได้รูปแบบไฟล์เบื้องต้นเป็นดังนี้
 
    ไฟล์ users.js เค้าโครง [models/users.js]
const db = require('../config/db')

const Users = {
    email_exists:((req, res) => {
        return new Promise((resolve, reject)=>{
 
        })
    }),
    login:((req, res) => {
        return new Promise((resolve, reject)=>{
     
        })
    }),
    register:((req, res) => {
        return new Promise((resolve, reject)=>{

        })
    }),
    userinfo:((req, res) => {
        return new Promise((resolve, reject)=>{  
      
        })
    })         
}

module.exports = Users
 
   ข้างต้นเป็นรูปแบบ Users Model ที่เราจะกำหนด โดยใน Users object จะมี property เป็นฟังก์ชั่นต่างๆ ที่เราจะใช้งาน
ในระบบสมาชิก การทำงานก็ตามชื่อที่กำหนด เช่น email_exists() สำหรับตรวจสอบอีเมล ไม่ให้ข้อมูลอีเมลของสมาชิกซ้ำกัน
เพราะในที่นี้ email ก็คือ username ที่เราจะใช้ล็อกอินเข้าสู่ระบบ นอกนั้นก็มีฟังก์ชั่น login() ฟังก์ชั่น register() และฟังก์ชั่น
userinfo()  โดยทั้ง 4 ฟังก์ชั่นจะมี parameter 2 ตัวเหมือนกันที่ส่งเข้ามา คือ req และ res สำหรับการทำงาน เราทราบอยู่
แล้วเบื้องต้นว่า ทั้ง 4 ฟังก์ชั่น จะทำงานร่วมกับฐานข้อมูล ดังนั้นในขั้นตอนการเพิ่ม หรือค้นหาข้อมูล จะมีช่วงที่ต้องรอใน
กระบวนการทำงาน  และเราไม่รู้ว่าจะเสร็จ ณ จุดเวลาไหน เราจึงสั่งให้ฟังก์ชั่น return เป็น Promise Object กลับมา
    หลักๆ ของการใช้งาน Promise ในฟังก์ชั่นทั้ง 4 จะอยู่ในรูปแบบการทำงานดังนี้
 
// จะส่งกลับแค่กรณีใด กรณีหนึ่งเท่านั้น คือ resolve หรือ ไม่ก็ reject
var promise = new Promise(function(resolve, reject) {
    if(!error){ 
      	resolve(value1)
    }else{
    	reject(value2)
    }
})
 
    และในขั้นตอนการใช้งานก็จะอยู่ใรูปแบบ
 
promise.then(
  function(result) { /* successful result */ },
  function(error) { /* error */ }
)
 
    ตัวอย่าง เช่น
 
promise.then(
  (value1)=>{
    console.log("completion " + value1)
  },
  (value2)=>{
    console.log("failure " + value2)   
  }
)
 
    จะได้โค้ดแบบเต็มพร้อมคำอธิบายเป็นดังนี้
 
    ไฟล์ users.js [models/users.js]
const db = require('../config/db')

const Users = {
    email_exists:((req, res) => {
        return new Promise((resolve, reject)=>{
            res.locals.user = req.body        
            db.then((db)=>{ // ใช้งานฐานข้อมูล เมื่อพร้อม
                db.collection('users') 
                .find({
                    email:req.body.email // ตรวจสอบอีเมล
                })
                .toArray( (error, results) => {
                    if(!error){ // ไม่มี error
                        if(results.length==0){  // ไม่มีอีเมลนี้ในระบบ ใช้สมัครสมาชิกได้
                            resolve(true)
                        }else{
                            reject('อีเมลนี้ถูกใช้งานแล้ว')
                        }
                    }else{
                        reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                    }
                })
            })
        })
    }),
    login:((req, res) => {
        return new Promise((resolve, reject)=>{
            res.locals.user = req.body        
            db.then((db)=>{ 
                db.collection('users') 
                .find({ // ตรวจสอบข้อมูลสำหรับล็อกอิน ด้วย email และ password ในฐานข้อมูล
                    email:req.body.email,  
                    password:req.body.password
                })
                .toArray( (error, results) => {
                    if(!error){ // ถ้าไม่มี error
                        if(results.length > 0){ // มีข้อมูล
                            resolve(results) // ส่งข้อมูลกลับออกไปใช้งาน
                        }else{
                            reject('อีเมล หรือ รห้สผ่านไม่ถูกต้อง กรุณาลองใหม่')
                        }
                    }else{
                        reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                    }
                })
            })            
        })
    }),
    register:((req, res) => {
        return new Promise((resolve, reject)=>{
            res.locals.user = req.body   
            db.then((db)=>{  
                db.collection('lastid')  // ดึงข้อมูล user_id ล่าสุดที่บันทึกไว้ใน "lastid" 
                .findOneAndUpdate({id:1},// เงื่อนไขที่ id = 1
                { $inc: { user_id: 1 }},(error, results)=>{  // แล้วให้เพิ่มค่า +1 เข้าไปให้กับ user_id
                    if(!error){ // ถ้าไม่มี error
                        let insertID = results.value.user_id+1 // เอาไอดีค่าล่าสุดมาบวกเพิ่ม แล้วไว้ในตัวแปร insertID
                        let user = { // ชุดข้อมูลที่จะบันทึก สำหรับสมัครสมาชิก
                            "_id": insertID, 
                            "name": req.body.name, 
                            "email": req.body.email, 
                            "password": req.body.password
                        }            
                        db.collection('users')
                        .insertOne(user, (error, results) => {  // เพิ่มข้อมูลสมาชิกใหม่
                            if(!error){ // ถ้าไม่มี error
                                resolve(results) // ส่งข้อมูลที่บันทึก กลับไปไว้ใช้งาน
                            }else{
                                reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                            }                          
                        })                             
                    }else{
                        reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                    }                                 
                })
            }) 
        })
    }),
    userinfo:((req, res) => {
        return new Promise((resolve, reject)=>{  
            db.then((db)=>{ 
                db.collection('users') 
                .find({
                    _id:req.session.userID  // แสดงข้อมูลผู้ใช้ที่ล็อกอิน และมี _id เท่ากับ session userID
                })
                .toArray( (error, results) => {
                    if(!error){ // ถ้าไม่มี error
                        if(results.length > 0){ // พบข้อมูล
                            resolve(results) // ส่งข้อมูลผู้ที่ล็อกอินกลับไป
                        }else{
                            reject('ไม่พบข้อมูลผู้ใช้')
                        }
                    }else{
                        reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                    }
                })
            })            
        })
    })         
}

module.exports = Users
 
    เมื่อเราได้ชุดคำสั่งทั้งหมดในระบบสมาชิก หรือ Users model เรียบร้อยแล้ว ขั้นตอนต่อไปก็คือการนำไปใช้งาน
ในหน้าต่างๆ เริ่มต้นที่ไฟล์ register.js สำหรับสมัครสมาชิก
 
    ไฟล์ register.js [routes/register.js]
const express = require('express')
const router = express.Router()
const { validation, schema } = require('../validator/users')
const Users = require('../models/users') // ใช้งาน Users model

router.route('/')
    .all((req, res, next) => { 
        // ตัวแปรที่กำหนดด้วย res.locals คือค่าจะส่งไปใช้งานใน template
        res.locals.pageData = {
            title:'Register Page'
        }
        // ค่าที่จะไปใช้งาน ฟอร์ม ใน template 
        res.locals.user = {
            name:'',
            email:'',
            password:'',
            confirm_password:''
        }
        // กำหนดหน้าที่ render กรณี error ไม่ผ่านการตรวจสอบข้อมูล
        req.renderPage = "pages/register"        
        next()
    })
    .get((req, res, next) => { 
        res.render('pages/register')    
    })
    .post(validation(schema.register), (req, res, next) => { 
        // เมื่อส่งข้อมูลมาสมัครสมาชิก ตรวจสอบอีเมล
        Users.email_exists(req, res).then(
            (validEmail)=>{ // ถ้าอีเมลยังว่าง ยังไม่ถูกใช้งาน
				// ทำการส่งข้อมูลไปบันทึกลงฐานข้อมูล
                Users.register(req, res).then(
                    (results)=>{ // บันทึกข้อมูลเรียบร้อย
						// ส่ง ข้อมูลไปแสดงในหน้า login ให้ผู้ใช้ล็อกอินเพื่อเข้าสู่ระบบ โค้ดด้านล่างตัดบรรทัดใหม่ไม่ให้ยาว
                        res.cookie('flash_message', 
                        	'Register Complete!! Please Login to access',
                        	{maxAge:3000})
                        res.redirect('/login')    
                    },
                    (error)=>{ // เกิด error แจ้ง error ในหน้า สมัครสมาชิก ส่งค่าเข้าไปด้วย res.locals.errors
                        res.locals.errors = {
                            "message": error
                        }
                        res.render('pages/register')    
                    }
                )  
            },
            (error)=>{ // เกิด error หรือ อีเมลไม่ว่าง ถูกใช้งานแล่้ว โค้ดด้านล่าง รวมให้อยู่บรรทัดเดียว ทำให้ดูเป็นตัวอย่าง
                res.locals.errors = { "message": error }
                res.render('pages/register')    
            }
        )
    })

module.exports = router
 
    ต่อด้วยหน้าล็อกอิน ส่งข้อมูลฟอร์มไปตรวจสอบและสร้างตัวแปร req.session.userID ถ้าล็อกอินสำเร็จ
 
    ไฟล์ login.js [routes/login.js]
const express = require('express')
const router = express.Router()
const { validation, schema } = require('../validator/users')
const Users = require('../models/users') // ใช้งาน Users model

router.route('/')
    .all((req, res, next) => { 
        // ตัวแปรที่กำหนดด้วย res.locals คือค่าจะส่งไปใช้งานใน template
        res.locals.pageData = {
            title:'Login Page'
        }
        // ค่าที่จะไปใช้งาน ฟอร์ม ใน template 
        res.locals.user = {
            email:req.session.email || '',
            password:req.session.password || '',
            remember:req.session.remember || ''
        }
        // กำหนดหน้าที่ render กรณี error ไม่ผ่านการตรวจสอบข้อมูล
        req.renderPage = "pages/login"
        next()
    })
    .get((req, res, next) => { 
        // ตรวจสอบว่ามี ค่า cookie ค่านี้หรือไม่
        if(req.cookies.flash_message){
            // ถ้ามี นำค่าไปใกำหนดในตัวแปร res.locals เพื่อใช้งานใน template
            res.locals.success = {
                message:req.cookies.flash_message
            }
        } 
        res.render('pages/login')    
    })
    .post(validation(schema.login), (req, res, next) => { 
        // สมมติเรากำหนดเป็น 15 วัน
        let MAX_AGE = 1000 * 60 * 60 * 24 * 15
        if(req.body.remember){
            req.session.email = req.body.email
            req.session.password = req.body.password
            req.session.remember = req.body.remember
            req.session.cookie.maxAge = MAX_AGE
        }else{
            delete req.session.email
            delete req.session.password
            delete req.session.remember
        }
		// ส่งข้อมูลไปตรวจสอบในฐานข้อมูล สำหรับทำการล็อกอินเข้าใช้งาน
        Users.login(req, res).then(
            (results)=>{ // ล็อกอินสำเร็จ พบข้อมูลผู้ใช้ตาม email และ password ที่แจ้ง
                console.log(results)
                req.session.userID = results[0]._id  // กำหนดตัวแปร session userID
                req.session.isLogined = true  // กำหนดตัวแปร session สถานะการล็อกอิน
                res.redirect('/me') // ไปยังหน้าสมาชิก
            },
            (error)=>{ // ล็อกอินไม่ผ่าน แจ้ง error หน้า ล็อกอิน
                console.log(error)
                res.locals.errors = {
                    "message": error
                }                
                res.render('pages/login')    
            }
        )

    })

module.exports = router
 
    และส่วนสุดท้ายหน้าสมาชิก ทำการดึงข้อมูลจากฐานข้อมูล โดยใช้ session userID ไปตรวจสอบ
และแสดงข้อมูล
 
    ไฟล์ dashboard.js [routes/dashboard.js]
const express = require('express')
const router = express.Router()
const Users = require('../models/users') // ใช้งาน Users model

router.route('/')
    .get((req, res, next) => { 
        res.locals.pageData = {
            title:'Dashboard Page'
        }     
		// ดึงข้อมูลสมาชิก จาก session userID
        Users.userinfo(req, res).then(
            (user)=>{  // มีข้อมูล
			   // ส่งข้อมูลไว้ในตัวแปร สำหรับใช้งานใน template
                res.locals.user = user[0]   
                res.render('pages/dashboard')                 
            }
        )       
    })

router.route('/logout')
    .get((req, res, next) => { 
	    // กรณี logout ลบ session ค่าที่เกี่ยวข้องกับระบบสมาชิก แล้วลิ้งค์ไปหน้าล็อกอิน
        delete req.session.isLogined
        delete req.session.userID
        res.cookie('flash_message', 'Logout Complete!!',{maxAge:3000})
        res.redirect('/login')    
    })    

module.exports = router
 
    ในหน้า สมาชิก เรามีการส่งข้อมูล ที่ได้มาจากการดึงจากฐานข้อมูลมาไว้ในตัวแปร res.locals.user เพื่อนำไป
แสดงในไฟล์ template ดังนั้น ให้เราแก้ไขไฟล์ template สำหรับแสดงข้อมูลสมาชิก เป็นดังนี้
 
    ไฟล์ dashboard.ejs [views/pages/dashboard.ejs]
<!doctype html>
<html>
    <?- include('../partials/head') -?>
<body>
<?- include('../partials/header') -?>
<?- include('../partials/nav') -?>

<div class="container">
    <h1 class="text-center">Hello</h1>
    <div class="text-center my-3 border border-success">
        <?= user.name ?>
        <br/>
        <?= user.email ?>
    </div>
    <div class="text-center">

    </div>
</div>

<?- include('../partials/footer') -?>
</body>
</html>
 
    เราใช้รูปแบบอย่างง่าย แสดงแค่ชื่อ กับอีเมล 
    
    ต่อไปเราทดสอบการทำงาน โดยให้ทำการสมัครสมาชิกเข้าไป จะได้ผลลัพธ์เป็นดังนี้ 
 
    กรอกข้อมูลหน้าสมัครสมาชิก 
 
 

 
 
    เสร็จแล้ว ทำการล็อกอินเข้าระบบ
 
 

 
 
    หน้าสมาชิก แสดงข้อมูลผู้ใช้
 
 

 
 
    ไปดูข้อมูลในฐานข้อมูล เมื่อเราทำการสมัครสมาชิกเข้ามา 
 
 

 
 
    จะเห็นว่าค่า user_id ที่อยู่ใน "lastid" จะเป็นค่า user_id ล่าสุดที่เราเพิ่มลงไปในฐานข้อมูล 
 
 

 
 
    เมื่อดูใน "users" จะเห็นว่า เราได้ทำการเพิ่ม user ใหม่เข้ามา ค่า _id เป็น primary key ที่เราใช้เก็บ user_id
มีค่าบวกเพิ่มจาก 1000 เป็น 1001 และเราเอาค่า 1001 อัพเดทกลับไปยัง "lastid" 
    ในข้อมูลข้างต้น เราจะเห็นว่า password ที่บันทึกไว้ ไม่ได้มีการเข้ารหัสเอาไว้ ซึ่งไม่มีความปลอดภัย ห้วข้อหน้า
เรามาดู การเข้ารหัส password ด้วย bcrypt 
 
 
 

การใช้งาน Bcrypt เข้ารหัส Password

    หลังจากเราได้ติดตั้ง bcrypt module สำหรับเข้ารหัสข้อมูลไปแล้ว ในตอนเริ่มต้นของบทความ ต่อไปเราจะมาดูวิธี
การใช้งานเบื้องต้นกัน โดยเราจะเลือกใช้ในรูปแบบ Promise ดังนี้
 
    การเข้ารหัสข้อมูล
bcrypt.hash(myPlaintextPassword, saltRounds).then(function(hash) {
    // นำค่า hash ไปใช้งาน เช่น บันทึกค่า hash ที่ได้ลงฐานข้อมูล
})
 
    การตรวจสอบข้อมูลที่เข้ารหัส
// ดึงค่า hash จากฐานข้อมูล แล้วนำค่ามาตรวนสอบ กับข้อมูลที่ต้องการ
bcrypt.compare(myPlaintextPassword, hash).then(function(result) {
    // result == true ถ้าข้อมูลถูกต้อง และ เป็น false ถ้าไม่ถูกต้อง
})
 
 
 

การประยุกต์ใช้ Bcrypt กับ ระบบสมาชิก

    ในการใช้งานในระบบสมาชิกของเรา จะมีอยู่ 2 ฟังก์ชั่น ที่เกี่ยวข้องกับการใช้งาน bcrypt คือฟังก์ชั่น register()
และฟังก์ชั่น login() ซึ่งอยู่ในไฟล์ users.js [models/users.js] ซึ่งเป็นไฟล์ Users Models  ให้แก้ไขไฟล์เป็นดังนี้
 
    ไฟล์ users.js [models/users.js]
const db = require('../config/db')
const bcrypt = require('bcrypt') // ใช้งาน bcrypt module
const saltRounds = 10 // กำหนดค่า salt

const Users = {
    email_exists:((req, res) => {
        return new Promise((resolve, reject)=>{
            res.locals.user = req.body        
            db.then((db)=>{ 
                db.collection('users') 
                .find({
                    email:req.body.email
                })
                .toArray( (error, results) => {
                    if(!error){
                        if(results.length==0){
                            resolve(true)
                        }else{
                            reject('อีเมลนี้ถูกใช้งานแล้ว')
                        }
                    }else{
                        reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                    }
                })
            })
        })
    }),
    login:((req, res) => {
        return new Promise((resolve, reject)=>{
            res.locals.user = req.body        
            db.then((db)=>{ 
                db.collection('users') 
                .find({
                    email:req.body.email
                })
                .toArray( (error, results) => {
                    if(!error){
                        if(results.length > 0){
                            let hash = results[0].password
                            let password = req.body.password
                            bcrypt.compare(password, hash).then((result)=>{
                                if(result == true) resolve(results)
                                if(result == false) reject('รห้สผ่านไม่ถูกต้อง กรุณาลองใหม่')
                            })                            
                        }else{
                            reject('อีเมล หรือ รห้สผ่านไม่ถูกต้อง กรุณาลองใหม่')
                        }
                    }else{
                        reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                    }
                })
            })            
        })
    }),
    register:((req, res) => {
        return new Promise((resolve, reject)=>{
            res.locals.user = req.body   
            db.then((db)=>{  
                db.collection('lastid')  
                .findOneAndUpdate({id:1},
                { $inc: { user_id: 1 }},(error, results)=>{ 
                    if(!error){
                        let password = req.body.password
                        bcrypt.hash(password, saltRounds).then((hash)=>{
                            let insertID = results.value.user_id+1
                            let user = {
                                "_id": insertID, 
                                "name": req.body.name, 
                                "email": req.body.email, 
                                "password": hash
                            }            
                            db.collection('users')
                            .insertOne(user, (error, results) => { 
                                if(!error){
                                    resolve(results)
                                }else{
                                    reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                                }                          
                            })       
                        })                                              
                    }else{
                        reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                    }                                 
                })
            }) 
        })
    }),
    userinfo:((req, res) => {
        return new Promise((resolve, reject)=>{  
            db.then((db)=>{ 
                db.collection('users') 
                .find({
                    _id:req.session.userID
                })
                .toArray( (error, results) => {
                    if(!error){
                        if(results.length > 0){
                            resolve(results)
                        }else{
                            reject('ไม่พบข้อมูลผู้ใช้')
                        }
                    }else{
                        reject('เกิดข้อผิดพลาด กรุณาลองใหม่')
                    }
                })
            })            
        })
    })         
}

module.exports = Users
 
    เสร็จแล้วให้เรา logout ออกจากสมาชิกทดสอบก่อนหน้า แล้วทำการลบข้อมูล เดิมในฐานข้อมูลออก ลบเฉพาะ
ข้อมูลใน "users" แล้วทำการเพิ่มข้อมูล โดยสมัครสมาชิกใหม่อีกครั้ง ซึ่งเดิม รหัสผ่านเราคือ 111111 เราจะใช้ค่า
เดิมทั้งหมด เมื่อสมัครสมาชิกใหม่เรียบร้อยด้วยข้อมูล จะได้ผลลัพธ์เป็นดังนี้
 
 

 
 
    จะเห็นว่าในส่วนของ password ที่เราพิมพ์เป็น 111111 จะถูกเข้ารหัสเรียบร้อย และส่วนของ _id ก็รันค่าเพิ่มอัตโนมัติ
และเมื่อเราทดสอบการล็อกอิน ก็สามารถล็อกอิน โดยกรอกรหัสผ่านเป็น 111111 ล็อกอินเข้าสู่ระบบได้ปกติ 
    เรามาลองทดสอบเงื่อนไข กรอกผิดทั้ง email และ password  กับกรอกผิดเฉพาะ password ดูว่าจะได้ผลลัพธ์
เป็นอย่างไร ตามลำดับ

 

 
 

 
 
    ตอนนี้ระบบสมาชิกของเรา มีขั้นตอนการทำงานถูกต้อง และเรียบร้อยไปพอสมควรแล้ว
 
    แต่ยังมีเงื่อนไขว่า เราจะส่งค่า session ไปหน้าอื่นๆ ยังไง เช่น ในไฟล์ nav.ejs ที่เป็น บางส่วนของ layout 
ที่มีการเรียกใช้ทุกๆ หน้า ถ้าเราต้องการใช้ตัวแปร session อย่าง isLogined มากำหนดการแสดงของเมนู ว่าถ้ายังไม่ได้ล็อกอิน
ให้แสดงเฉพาะเมนู login และ register และถ้าล็อกอินแล้ว ให้แสดงเฉพาะเมนู logout   ซึ่งเราทราบดีว่า ถ้าจะส่งค่าไปใช้งาน
ใน template เราก็แค่กำหนด res.locals โดยเพิ่ม property ค่าที่ต้องการ เช่น
 
if(typeof req.session !== 'undefined'){
	res.locals.session = req.session
}
 
    นั่นหมายความว่า เราต้องไปไล่กำหนดในทุกๆ หน้า เช่น หน้า index.js , login.js ..... dashboard.js ซึ่งคงไม่สะดวกแน่
    ดังนั้น เราจะสร้างฟังก์ชั่น middleware มาจัดการ ดังนี้ 
    ให้เราสร้างไฟล์ session.js ไว้ในโฟลเดอร์ config แล้วกำหนดโค้ดเป็นดังนี้
 
    ไฟล์ session.js [config/session.js]
module.exports = {
    useSession:(req, rew, next)=>{
        // ถ้ามีตัวแปร sewsion 
        if(typeof req.session !== 'undefined'){
            // ให้กำหนดต่าให้กับ app.locals ใน property ชื่อ session
            // ให้มีค่าเท่ากับ session object 
            req.app.locals.session = req.session
        }    
        next()
    }
}
 
    การใช้คำสั่ง req.app.locals คือการกำหนดค่าให้กับ app.locals อย่างที่เราทราบว่า res.locals จะเป็นการกำหนด
scope ของตัวแปร ให้สามารถใช้งานได้ในหน้า view หรือ template ที่ render แต่การใช้งาน app.locals เป็นการกำหนด
scope ของตัวแปร ให้สามารถใช้งานได้ทั้ง web หรือทั้ง app ทุก view และ template   การกำหนด 
req.app.locals.session = req.session หรือก็คือ  app.locals.session = req.session จะทำให้เราสามารถเรียกใช้งาน
ตัวแปร session ใน template ต่างๆ ได้  
    สมมติว่า ตอนนี้ตัวแปร session ใน req.session มีแค่ req.session.isLogined กับ req.session.userID
การกำหนดให้ req.app.locals.session = req.session ก็จะทำให้เราสามารถใช้งานตัวแปร app.locals.session
ที่ชื่อ session.isLogined กับ session.userID ในไฟล์ template  ได้ แบบนี้เป็นต้น
    หากกลัวสับสน ก็อาจจะใช้เป็น req.app.locals._session แทนก็ได้ เวลาเรียกใช้ใน template ก็จะเป็น 
_session.isLogined กับ _session.userID 
 
    หลังจากสร้างไฟล์ sesson.js เรียบร้อยแล้ว ให้เราเรียกใช้งานในไฟล์ app.js ดังนี้
 
    ไฟล์ app.js
const express = require('express')  // ใช้งาน module express
const app = express()  // สร้างตัวแปร app เป็น instance ของ express
const path = require('path') // เรียกใช้งาน path module
const cookieParser = require('cookie-parser')
const session = require('express-session')
const store = require('./config/storeDb')
const { authorize } = require('./config/auth')
const { useSession } = require('./config/session')
const createError = require('http-errors') // เรียกใช้งาน http-errors module
const port = 3000  // port 
 
// ส่วนของการใช้งาน router module ต่างๆ 
const indexRouter = require('./routes/index')
const loginRouter = require('./routes/login')
const registerRouter = require('./routes/register')
const dashboardRouter = require('./routes/dashboard')
 
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.set('view options', {delimiter: '?'});
// app.set('env','production')
 
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'public')))

//app.set('trust proxy', 1) // trust first proxy
app.use(session({
    name:'sid', // ถ้าไม่กำหนด ค่าเริ่มต้นเป็น 'connect.sid'
    secret: 'my ses secret',
    store:store,
    resave: true,
    saveUninitialized: true
}))
app.use(useSession)

 
// เรียกใช้งาน indexRouters
app.use('/', indexRouter)
app.use('/login', authorize('/me', false), loginRouter)
app.use('/register', authorize('/me', false), registerRouter)
app.use('/me', authorize('/login', true), dashboardRouter)

// ทำงานทุก request ที่เข้ามา 
app.use(function(req, res, next) {
    var err = createError(404)
    next(err)
})
 
// ส่วนจัดการ error
app.use(function (err, req, res, next) {
    // กำหนด response local variables 
    res.locals.pageData = {
        title:'Error Page'
    }    
    res.locals.message = err.message
    res.locals.error = req.app.get('env') === 'development' ? err : {}
 
    // กำหนด status และ render หน้า error page
    res.status(err.status || 500) // ถ้ามี status หรือถ้าไม่มีใช้เป็น 500
    res.render('pages/error') 
})
 
app.listen(port, function() {
    console.log(`Example app listening on port ${port}!`)
})
 
    เสร็จแล้ว เรามาลองเรียกใช้ และกำหนดค่าในไฟล์ nav.ejs เป็นดังนี้
 
    ไฟล์ nav.ejs [views/partials/nav.ejs]
<nav class="text-center">
THIS IS NAV <br>
<? if(!session.isLogined || session.isLogined == false){ ?>
<a href="/login">Login</a> | <a href="/register">Register</a>
<? }else{ ?>
 <a href="/me/logout">Logout</a>
<? } ?>
</nav>
 
    ทดสอบใช้งาน ไปหน้าแรกของ Homepage ที่ path:"/" เมื่อเราได้ทำการล็อกอินอยู่
 
 

 
 
    และเมื่อเรา logout
 
 

 
 
    จะเห็นว่าส่วนของเมนู ใน nav.ejs มีการใช้งานค่าตัวแปร ที่เราส่งมา กำหนดเงื่อนไขในการแสดงของเมนู
 
    เนื้อหาตอนหน้า เรายังอยู่ในเนื้อหาเกี่ยวกับระบบสมาชิก จะมาดูในเรื่องของการป้องการ Cross-Site Request Forgery 
หรือ XRSF / CSRF attack เช่น การส่งข้อมูลจากที่อื่น มาทำการล็อกอินระบบเว็บของเรา / หรือการอาศัยการคงอยู่ของ
session ในระบบของเราเข้ามาโจมตีเว็บไซต์ คร่าวๆ ประมาณนี้เป็นต้น รอติดตาม


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ









เนื้อหาที่เกี่ยวข้อง






เนื้อหาพิเศษ เฉพาะสำหรับสมาชิก

กรุณาล็อกอิน เพื่ออ่านเนื้อหาบทความ

ยังไม่เป็นสมาชิก

สมาชิกล็อกอิน



( หรือ เข้าใช้งานผ่าน Social Login )




URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ