การใข้งาน Cookie และ Session ใน Express สำหรับ ระบบสมาชิก

เขียนเมื่อ 4 ปีก่อน โดย Ninenik Narkdee
connect-mongodb expressjs nodejs cookie session

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

ดูแล้ว 14,999 ครั้ง


เนื้อหาต่อไปนี้ เรามาต่อในเรื่องของระบบสมาชิก โดยจะพูดถึง
การใช้งาน Cookie / Session และการนำมาใช้งาน
    ก่อนที่เราจะสามารถจัดการเกี่ยวกับ cookie ได้นั้น เราจำเป็น
ต้องใช้งาน Middleware module ที่ชื่อ cookie-parser เพื่อใช้สำหรับแปลงข้อมูล
ที่เป็น Cookie header และให้สามารถเรียกใช้งานผ่าน req.cookies ได้
 

การติดตั้ง Cookie-parser

    โดยให้ทำการติดตั้ง cookie-parser ด้วยคำสั่ง
 
npm install cookie-parser --save
    หลังจากติดตั้งเรียบร้อยแล้ว ให้เรากำหนดในไฟล์ app.js โดยเพิ่มส่วนของการใช้งาน module 
const cookieParser = require('cookie-parser') // ใช้งาน cookie-parser module
    และเรียกใช้ middleware ฟังก์ชั่นด้วยคำสั่ง
app.use(cookieParser())
 
 
 

การใช้งาน Cookie ใน Exppress

    เราจะอธิบายการใช้งานเกี่ยวกับ cookie โดยจะใช้หน้า Home page สำหรับสาธิตใช้งานโค้ดต่างๆ
    ไฟล์ index.js [routes/index.js]
const express = require('express')
const router = express.Router()

router.get('/', function(req, res, next) {
    res.locals.pageData = {
        title:'Home Page'
    }  
    // สำหรับทดสอบว่าใช้งาน cookie ได้แล้วหรือไม่ 
    // หาก req.cookies ไม่เท่ากับ undefined เราก็สามารถใช้งาน cookie ได้
    if(typeof req.cookies !=='undefined'){
        // สามารถใช้งานตัวแปร cookie ได้เช่น
        // การกำหนดค่า ตัวอย่าง 
        // res.cookie('my_ck1', 'cookie_1',{maxAge:60000})
        // การดึงค่ามาใช้งาน req.cookies.my_ck1 จะได้ค่าเป็น cookie_1
    }
	res.render('pages/index')
})

module.exports = router
 
    ปกติเมื่อเราทำการติดตั้ง และเรียกใช้งาน cookie-parser middleware แล้วคำสั่งตรวจสอบ
ข้างต้นก็ไม่จำเป็นต้องใช้งาน โดยเราสามารถแสดงข้อมูล cookies ผ่าน req.cookies object และ
สามารถกำหนดตัวแปร cookie ผ่านรูปแบบ res.cookie(name, value [, options])
ดูการใช้งานการกำหนด cookie เพิ่มเติได้ที่ Cookies
    จริงๆ แล้วหรือโดยทั่วไป ตัวแปร cookie จะใช้สำหรับติดตามการทำงานอย่างใดอย่างหนึ่ง หรือที่เรียกว่า
tracking มากกว่า ที่จะใช้สำหรับเก็บข้อมูลที่สำคัญๆ การใช้ตัวแปร cookie ทั่วๆ ไปอาจจะไม่จำเป็นต้องทำการ sign
(การ Signing คือ กระบวนการที่ทำให้มั่นใจได้ว่าข้อมูลนั่นๆ จะไม่ถูกเปลี่ยนแปลงหรือดัดแปลง หรือเข้าใจอย่างง่าย
คือ การ Signing คือการบอกให้ทราบว่าค่านั้นถูกระบุแน่ชัดว่าใครเป็นคนสร้างและมั่นใจได้ว่าเป็นค่าที่ถูกต้องปลอดภัย)
หากมีการทำการ Cookies Signing เราสามารถเรียกใช้งาน cookies ผ่าน req.signedCookies 
 

    การตรวจสอบค่า cookies

    เราสามารถใช้คำสั่งต่อไปนี้ ในการตรวจสอบว่า มี cookies ชื่อนี้อยู่หรือไม่ 
 
if(typeof req.cookies.my_ck1 === 'undefined'){  //  }
if(req.cookies.my_ck1 === undefined){ // }
if(!req.cookies.my_ck1){ // }
 
    ทั้ง 3 รูปแบบเป็นเงื่อนไขสำหรับตรวจสอบตัวแปร cookies ชื่อ my_ck1 ว่ามีการกำหนดค่าอยู่แล้วหรือไม่
    

    การกำหนดค่า cookies

    เราสามารถใช้คำสั่ง การกำหนดค่า cookies ในรูปแบบ 
 
res.cookie(name, value [, options])
    จะเห็นว่า การกำหนดค่าให้กับ cookies เป็นการทำงานที่ส่งมาจากฝั่ง server โดยส่งมาใน Response Header 
ที่ชื่อ "Set-Cookie" เรามาลองทดสอบสร้างตัวแปร cookie ในหน้า home โดยถ้าไม่มีการกำหนดค่า cookie นี้อยู่
ให้ทำการกำหนดค่าขึ้นมา และให้มีอายุ 1 นาที โดยเรากำหนดโดยใช้ maxAge:6000 จะได้เป็น
 
    ไฟล์ index.js บางส่วน [routes/index.js]
router.get('/', function(req, res, next) {
    res.locals.pageData = {
        title:'Home Page'
    }  
    if(!req.cookies.my_ck1){
        res.cookie('my_ck1', 'cookie_1',{maxAge:60000})
    }
	res.render('pages/index')
})
 
    เมื่อเราเรียกไปหน้าแรก path: "/" แล้วตรวจสอบผ่าน Dev Inspector ใน Chrome จะได้ผลลัพธ์ดังรูป
 
 

 
 
Set-Cookie: my_ck1=cookie_1; Max-Age=60; Path=/; Expires=Sat, 11 May 2019 18:03:43 GMT
 
    ตัวแปร cookie ที่ชื่อ my_chk จะมีค่าเท่ากับ "cookie_1" และจะหมดอายุใน 60 วินาที (1 นาที) ตามค่า
maxAge ที่เรากำหนดเท่ากับ 60000 ในหน่วยมิลลิวินาที
    หากเราเรียกไปยังหน้าอื่นๆ เช่นไปหน้า path: "/login" หากตัวแปร cookie นี้ยังไม่หมดอายุ ตัวแปร cookie จะถูกส่ง
ไปในหน้าต่างๆ ผ่าน Request Header ที่ชื่อ "Cookie" ตามรูปด้านล่าง

 

 
 
    ดังนั้นเราก็สามารถใช้งานตัวแปร cookie นี้ในหน้า login ได้
    ถ้าจำเกี่ยวกับตัวแปร res.locals ที่ใช้สำหรับส่งค่าตัวแปรไปใช้งานใน template ที่ทำการ render ในบทความที่ผ่าน
มาได้ ความแตกต่างของกรณี้นั้น กับกรณีของตัวแปร cookie คือ เราสามารถใช้งาน cookie กรณีที่เป็นการเปลี่ยนหน้า
หรือลิ้งค์ไปยังหน้าใหม่ อย่างเช่นใช้คำสั่ง res.redirect() ซึ่งเราไม่สามารถใช้ค่าจากตัวแปร res.locals ได้โดยตรง
ต้องรับค่าจากตัวแปร cookie แล้วค่อยมากำหนดในตัวแปร res.locals เพื่อเรียกใช้งานใน template อีกที
 

    การลบตัวแปร cookies

    ปกติตัวแปร cookie จะหายไปเมื่อถึงกำหนดเวลาที่ระบุด้วย "expires" (วันที่) หรือ "maxAge" (วินาที / ใน express จะ
กำหนดเป็น มิลลิวินาที) หากไม่ได้กำหนด จะมองเป็น cookie session ที่เมื่อปิดหน้าบราวเซอร์นั่นไป ตัวแปร cookie ก็จะ
ถูกลบหายไปด้วย นอกจากนั้น เราสามารถทำการลบตัวแปร cookies ด้วยคำสั่งในรูปแบบ
 
    res.clearCookie(name [, options])
    // ตัวอย่างเช่น res.clearCookie('my_ck1')
 
 
 
 
 

การประยุกต์ใช้งาน Cookie 

    เพื่อให้เห็นภาพชัดเจนขึ้น เราจะใช้ตัวแปร cookie เก็บค่าข้อความ ที่แจ้งว่า ผู้ใช้ได้ทำการออกจากระบบเรียบร้อยแล้ว
ในหน้า login หลังจากที่ลิ้งค์มาจาก path: "/me/logout"
    ไฟล์ dashboard.js บางส่วน [routes/dashboard.js]
router.route('/logout')
    .get((req, res, next) => { 
        res.cookie('flash_message', 'Logout Complete!!',{maxAge:3000})
        res.redirect('/login')    
    })    
 
    เมื่อผู้ใช้กดลิ้งค์ logout เราก็ทำการสร้างตัวแปร cookie ที่ชื่อ flash_message มีค่าตามข้อความที่กำหนด และให้
หมดอายุใน 3 วินาที โดยหลังจากสร้าง cookie แล้วก็ลิ้งค์ไปที่หน้า login โดยใน path: "/login" เราก็สร้างตัวแปร
res.locals.success กำหนด message property เป็นค่าข้อความที่ส่งมาจาก req.cookies.flash_message 
 
    ไฟล์ login.js บางส่วน [routes/login.js]
.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')    
})
 
    เมื่อเราได้ตัวแปร res.locals.success เราก็สามารถเรียกใช้งาน message ที่เรากำหนด มาใช้ในไฟล์ template ได้ดังนี้
 
    ไฟล์ login.ejs บางส่วน [views/pages/login.ejs]
<? if(typeof success !== 'undefined'){ ?>
<div class="form-group row">
	<div class="col-7 mx-auto">
		<div class="alert alert-success alert-dismissible fade show" role="alert">
		<?= success.message ?>
		<button type="button" class="close" data-dismiss="alert" aria-label="Close">
			<span aria-hidden="true">&times;</span>
		</button>
		</div>    
	</div>
</div>
<? } ?>   
    ตัวอย่างผลลัพธ์ ที่ได้
 

 
 
    เมื่อหน้าล็อกอินโหลด หลังจากทำการ logout ข้อความที่เก็บในตัวแปรจะอยู่แค่ 3 วินาที นั้นหมายความว่า เมื่อเรากดรีเฟรส
หน้านี้อีกครั้ง ตัวแปร cookie ก็ไม่มีแล้ว ข้อความก็จะไม่แสดง
 
 

    การใช้งาน Cookie Signing

    ปกติ Cookie สามารถสร้างโดยให้ server ส่ง HTTP header เพื่อบอกให้บราวเซอร์สร้างตัวแปร cookie 
ผ่านการกำหนดค่า โดยใช้ "Set-Cookie"  property ในรูปแบบ
        Set-Cookie: <cookie_name>=<cookie_value>
 
    อีกวิธ๊คือสร้างตัวแปร Cookie ฝั่ง Client โดยใช้คำสั่ง JavaScript ในรูปแบบคำสั่ง
        document.cookie
 
    ดังนั้น หากเราต้องการป้องกันไม่ให้ฝั่ง Client หรือใครสามารถเปลี่ยนค่าตัวแปร cookies ที่สร้างโดยฝั่ง Server เราสามารถ
ใช้กระบวนการ Signed หรือการประทับตราว่า cookie นี้ถูกสร้างผ่าน Server ที่มีการเพิ่มค่า secret ที่เข้ารหัสแล้ว ต่อท้ายเข้า
ไปในค่า cookie นั้น ดูตัวอย่างตัวแปร แบบ ไม่ได้ Signed และ Signed ด้านล่างตามลำดับ
    Set-Cookie: my_ck1=cookie_1; Path=/
    Set-Cookie: my_ck1=s%3Acookie_1.YO4qiNRTcUAGiKzM6P7dNfNSsQh%2FxAmXRONikSoH76k; Path=/
 
    สังเกตว่าการ Signed cookie ไม่ใช่การเข้ารหัสค่า ของ cookie ทั้งหมด เรายังเห็นค่า cookie แต่จะมีค่า secret ที่
กำหนดในฝั่ง server ที่เข้ารหัสแล้วต่อท้ายเข้ามาในค่า cookie มีผลให้ขนาดของ cookie เพิ่มขึ้น โดยการใช้งาน cookie 
จะสามารถ กำหนดความยาวรวมไม่เกิน 4093 bytes
 
    ให้เราทำการเพิ่ม secret ให้เข้าไปในขั้นตอนการเรียกใช้งาน cookieParser middleware ในไฟล์ app.js เป็นดังนี้
app.use(cookieParser('mysecret'))  // เปลี่ยนค่า "myscret" เป็นค่าตามต้องการ
    เมื่อมีการกำหนดให้ใช้งานการ sign cookie แล้ว  ตัวแปร cookies ที่กำหนด โดยไม่ได้ระบุ signed หรือระบุ signed 
เท่ากับ false ตัวแปร cookie นั้นๆ จะยังสามารถเรียกใช้งานผ่าน req.cookies ได้ ตัวอย่างเช่น 
 
res.cookie('flash_message', 'Logout Complete!!',{maxAge:3000})
res.cookie('flash_message', 'Logout Complete!!',{signed: false, maxAge:3000})
// การกำหนดตัวแปรโดยไม่ระบุ signed หรือระบุค่าเป็น false จะยังสามารถเรียกใช้งานผ่านตัวแปร 
// req.cookies.flash_message ได้
 
    สำหรับตัวแปร cookie ที่ระบุค่า signed เป็น true หรือก็คือให้ทำการ sign ค่า cookie  เช่น
 
res.cookie('flash_message', 'Logout Complete!!',{signed: true, maxAge:3000})
// การกำหนดตัวแปรโดยระบุค่า signed เป็น true เราจะไม่สามารถเรียกใช้งาน req.cookies.flash_message ได้
// ต้องเรียกใช้งานผ่านตัวแปร req.signedCookies.flash_message  แทน 
 
    ข้างต้นเป็นแนวทางการใช้งาน Signed cookie สำหรับ flash_message เป็นเพียงข้อความที่จะแสดงเพียงชั่วคราว
เท่านั้น ไม่จำเป็นต้องใช้งานการ Signed ก็ได้ โดยไม่ได้ระบุ signed หรือระบุ signed เท่ากับ false
 
    ก่อนจบเนื้อหาหัวข้อเกี่ยวกับ cookie เราจะมาประยุกต์เพิ่มเติมการใช้งาน cookie กับระบบสมาชิก โดยให้จดจำค่า
email และ password เมื่อผู้ใช้เลือก ว่าต้องการจำค่าไว้  ให้เราเพิ่มส่วนตัวเลือกจดจำ email และ password ใน
template หน้าล็อกอิน ดังนี้
 
    ไฟล์ login.ejs บางส่วน [views/pages/login.ejs]
<div class="form-group row">
    <label class="col-7 mx-auto" style="padding-left:40px;" for="remember">
        <input type="checkbox" class="form-check-input" 
        id="remember" name="remember" value="true" 
        <?= (user.remember=='true')?"checked":"" ?>> 
        Remember?
    </label>
</div>   
 
    ต่อไปไฟล์ ตรวจสอบความถูกต้องข้อมูล ให้เราเพิ่มรูปแบบให้กับ ค่า remember
 
    ไฟล์ users.js บางส่วน [validator/users.js]
login : Joi.object().keys({
	email: Joi.string().email({ minDomainSegments: 2 }).required(),
	password:Joi.string().min(6).max(15).required(),
	remember:Joi.any()
})
 
    และสุดท้ายส่วนของการจำลองการล็อกอิน เมื่อผู้ใช้ส่งข้อมูล POST Request มายังหน้าล็อกอิน 
เราจะทำการสร้าง cookie 3 ค่า หากผู้ใช้เลือกให้จดจำข้อมูลโดยติ้กเลือกตรง remember
และถ้าหากผู้ใช้ติ้กไม่ให้จดจำข้อมูลออก ก็ให้ทำการล้างค่า cookie ทั้ง 3 ในกรณีล็อกอินเข้ามาใหม่
 
    ไฟล์ login.js [routes/login.js]
const express = require('express')
const router = express.Router()
const { validation, schema } = require('../validator/users')

router.route('/')
    .all((req, res, next) => { 
        // ตัวแปรที่กำหนดด้วย res.locals คือค่าจะส่งไปใช้งานใน template
        res.locals.pageData = {
            title:'Login Page'
        }
        // ค่าที่จะไปใช้งาน ฟอร์ม ใน template 
        res.locals.user = {
            email:req.cookies.email || '', // ใช้ค่า cookie ถ้ามี ถ้าไม่มีใช้ค่าว่าง
            password:req.cookies.password || '',
            remember:req.cookies.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.cookie('my_ck1','cookie_1',{signed:true})
        res.render('pages/login')    
    })
    .post(validation(schema.login), (req, res, next) => { 
        // ผ่านการรวจสอบ ลิ้งค์ไปหน้า /me
		// กำหนดอายุของ cookie ใน 7 วัน
        let MAX_AGE = 1000 * 60 * 60 * 24 * 7
        if(req.body.remember){ // ถ้ามีการให้จำข้อมูล สร้าง cookie 
            res.cookie('email', req.body.email,{maxAge:MAX_AGE})
            res.cookie('password', req.body.password,{maxAge:MAX_AGE})
            res.cookie('remember', req.body.remember,{maxAge:MAX_AGE})
        }else{ // หากเลือกไม่ต้องจดจำข้อมูล ล้างค่า cookie
            res.clearCookie('email')
            res.clearCookie('password')
            res.clearCookie('remember')
        }
        res.redirect('/me')
    })

module.exports = router
 
    ทดสอบเมื่อล็อกอินเข้าสู่ระบบ เบื้องต้นโดยกรอกข้อมูลถูกต้องผ่าน พร้อมกับเลือกให้จดจำข้อมูล
เมื่อเรากลับมาหน้าล็อกอินอีกครั้ง ซึ่งอาจจะหลังจาก logout ค่าของข้อมูลก็ยังอยู่ เพราะเป็นค่า cookie
ที่เรากำหนดดังรูป

 

 
 
    การจำลองการจดจำข้อมูลข้างต้น ยังไม่ได้มีการเข้ารหัสค่าของข้อมูล เรายังจะเห็นข้อมูล cookie ต่างๆ
 
 

 
 
    อย่างไรก็ตามทั้งนี้ เราต้องการแค่ให้เห็นแนวทางการประยุกต์ กับระบบการจดจำค่าข้อมูล เท่านั้น การปรับแต่ง
เพิ่มเติม เพื่อรองรับความปลอดภัย จะได้อธิบายในลำดับถัดไป
 
 
 

การใช้งาน Session ใน Express

    ปกติเราจะเห็นการใช้งานตัวแปร session สำหรับเก็บข้อมูลที่มีความสำคัญ และถูกบันทึกที่ฝั่ง server ทำให้ข้อมูล
มีความปลอดภัยมากกว่าการใช้งาน cookie    ใน Express หากเราต้องการใช้งาน session เราต้องทำการติดตั้ง Middleware
ฟังก์ชั่นที่ชื่อ express-session โดยใช้คำสั่ง
 
npm install express-session --save
    หลังจากติดตั้งเรียบร้อยแล้ว ให้เรากำหนดในไฟล์ app.js โดยเพิ่มส่วนของการใช้งาน module 
const session = require('express-session') // ใช้งาน express-session module
    และเรียกใช้ middleware ฟังก์ชั่นด้วยคำสั่ง
app.use(session({
    name:'sid', // ถ้าไม่กำหนด ค่าเริ่มต้นเป็น 'connect.sid'
    secret: 'my ses secret',
    resave: true,
    saveUninitialized: true
}))
    ในการใช้งาน middleware ยังมี options ที่เราสามารถกำหนดเพิ่มเติมได้ จะอธิบายส่วนที่สำคัญๆ ไปตามลำดับ
 

    การกำหนดค่า session

req.session.email = 'john.doe@gmail.com' 
// หรือ 
req.session.userdata = {
	a:1,
	b:2
}
 

    การอ่านค่า หรือเรียกใช้งานตัวแปร session เช่น

let email = req.session.email
 

    การลบตัวแปร session เช่น

delete req.session.email 
 

    การลบตัวแปร session ทั้งหมด 

req.session.destroy(function(err) {
  // ลบตัวแปร session ทั้งหมด 
})
 
 
    ในการใช้งานตัวแปร session เมื่อมีการเรียกใช้งาน จะมีแค่ค่า session ID เท่านั้นที่ถูกบันทึกเป็น cookie ไว้ที่ฝั่ง
client หรือผู้ใช้ โดยจะมีชื่อตามค่า name ที่เรากำหนด หากไม่กำหนดจะใช้เป็น 'connect.sid' ในข้างตันเรากำหนด
เป็น 'sid'  ดังนั้น เราสามารถเรียกใช้งานค่านี้ผ่านตัวแปร cookie ด้วย req.cookies.sid
    อย่างไรก็ตาม หากมีการกำหนด secret ให้กับ การใช้งาน app.use(cookieParser('mysecret')) จะมีผลให้ค่า 
req.cookies.sid เป็น undefined ดังนั้น ให้เรายกเลิกการกำหนดค่า secret ให้กับตัวแปร cookie โดยใช้เป็น 
app.use(cookieParser())  เท่านี้เราก็สามารถเรียกใช้งาน req.cookies.sid ได้ โดยจะมีค่าที่ถูก signed ด้วย secret
ที่กำหนดในการใช้งาน session ต่อท้ายมาด้วย สำหรับค่า session ID ในฝั่ง server เราสามารถเรียกใช้งานผ่านตัวแปร
req.sessionID หรือ req.session.id  ลองมาดูตัวอย่าง ค่าทั้ง  3 ตามลำดับ 
 
req.cookies.sid = s:0bil-4VB3NmsT9xXB4_Wr_aa7uZzkEk2.qWuxhgVxHJSdwTEz560LNZsSGFaZ5E+rVrPclhFLjkU
req.sessionID = 0bil-4VB3NmsT9xXB4_Wr_aa7uZzkEk2
req.session.id = 0bil-4VB3NmsT9xXB4_Wr_aa7uZzkEk2
    หากมีการเปลี่ยนแปลงของค่า session ID ในฝั่ง server ค่า req.cookies.sid จะเปลี่ยนค่าตามเมื่อทำการโหลดหน้านั้น
ใหม่อีกครั้ง
    สำหรับค่าตัวแปร session อื่นๆ ที่เราสร้างเพิมเติม จะไม่ถูกบันทึกหรือสร้างเป็นตัวแปร cookie ในฝั่ง client แต่จะถูกบันทึก
เก็บไว้ที่ฝั่ง server แทน
    เราจะมาลองประยุกต์ใช้งานตัวแปร session สำหรับจดจำค่าข้อมูลหน้าล็อกอินแทน cookie ดังนี้
 
    ไฟล์ login.js [routes/login.js]
const express = require('express')
const router = express.Router()
const { validation, schema } = require('../validator/users')

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.cookie('my_ck1','cookie_1',{signed:true})
        res.render('pages/login')    
    })
    .post(validation(schema.login), (req, res, next) => { 
        // ผ่านการรวจสอบ ลิ้งค์ไปหน้า /me
        if(req.body.remember){
            req.session.email = req.body.email
            req.session.password = req.body.password
            req.session.remember = req.body.remember
        }else{
            delete req.session.email
            delete req.session.password
            delete req.session.remember
        }
        res.redirect('/me')
    })

module.exports = router
 
    ทดสอบและรัน แล้วดูผลลัพธ์
 
 

 
 
    จะเห็นว่าสามารถใช้งานแทน cookie ได้ และด้วยที่ข้อมูลบันทึกไว้ที่ฝั่ง server ค่าต่างๆ จึงปลอดภัยกว่า cookie
ที่เดิม เราจะเห็นค่าที่จดจำแสดงในส่วนของ cookie ฝั่งผู้ใช้ 
    อย่างไรก็ตาม ตัวแปร session โดยทั่วไป ค่าของตัวแปร จะอยู่จนกว่า จะปิดหน้าบราวเซอร์นั่นๆ ไป ซึ่งหากเปิดกลับ
และเรียกใช้งาน จะพบว่าข้อมูลที่เราเก็บไว้ใน session ไม่อยู่แล้ว สิ่งที่จะใช้กำหนดอายุของ ตัวแปร session ในกรณีนี้
คือการกำหนด maxAge หรือ expires ให้กับ cookie ที่เป็น session ID 
    เข้าใจอย่างง่าย session ID ก็เปรียบเสมือน primary key ของ แถวข้อมูลในตาราง session ค่า primary key นี้ จะไป
ดึงข้อมูลฟิลด์ต่างๆ ของ session ที่เรากำหนดไว้ในฝั่ง server ถ้าเราปิดบราวเซอร์ โดยไม่ได้กำหนดวันหมดอายุให้กับตัวแปร
cookie ของ session ID แล้ว   ค่าก็จะเปลี่ยนไปเมื่อเปิดขึ้นมาใหม่  พอค่าเปลี่ยน หรือ primary key เปลี่ยน ข้อมูลก็
ไม่ตรงกัน ไม่สามารถอ้างอิง และดึงข้อมูล session ที่บันทึกไว้ได้ เราสามารถกำหนด maxAge หรือ expires ในขึนตอน
การเรียกใช้งาน middleware ในไฟล์ app.js ในรูปแบบดังนี้
 
app.use(session({
    name:'sid', // ถ้าไม่กำหนด ค่าเริ่มต้นเป็น 'connect.sid'
    secret: 'my ses secret',
    resave: true,
    saveUninitialized: true,
    cookie: { maxAge: 3600000 }
}))
 
    หรือจะกำหนดในขึ้นตอนการกำหนดค่าก็ได้ เช่น
// เลือกอย่างใด อย่างหนึ่ง ระหว่าง expires กับ maxAge ในที่นี้แนะนำเป็น maxAge
let hour = 3600000
req.session.cookie.expires = new Date(Date.now() + hour)
req.session.cookie.maxAge = hour
    สมมติว่าเราต้องการให้ตัวแปร cookie sid อยู่แค่ 1 นาทีก็กำหนด เป็น
req.session.cookie.maxAge = 60000
 
    ถ้าเราต้องการดูว่าตัวแปร cookie sid (ก็คือ session ID) เหลือเวลาอีกเท่าไหร่จึงจะหมดอายุ ก็สามารถ
เรียกดูค่าได้จาก req.session.cookie.maxAge  อย่างสมมติว่า หลังจากสร้างตัวแปร cookie sid มาแล้ว 30 วินาที
แล้วเราแสดงค่า req.session.cookie.maxAge ก็จะได้เท่ากับ 30000 หรือก็คือ เหลืออีก 30 วินาทีที่ sid นี้จะหมดอายุ
เราสามารถเรียกดูค่า maxAge ที่กำหนดได้ ผ่านค่า req.session.cookie.originalMaxAge ซึ่งจะได้ค่าเป็น 60000 หรือ
ก็คือตัวแปร sid นี้กำหนดให้หมดอายุใน 60 วินาทีหรือ 1 นาทีนั่นเอง
    ในที่นี่เราจะไม่กำหนดที่ไฟล์ app.js แต่จะกำหนดในตอนใช้งาน คือ ในตอนที่สร้าง session เพื่อให้จำ ค่า session
ข้อมูลหน้าล็อกอิน เราก็กำหนดเป็น ให้จำข้อมูล 15 วัน หรือก็คือให้ตัวแปร cookie sid อยู่ได้นาน 15 วัน จะได้เป็น
 
    ไฟล์ login.js บางส่วน [routes/login.js]
.post(validation(schema.login), (req, res, next) => { 
	// ผ่านการรวจสอบ ลิ้งค์ไปหน้า /me
	// สมมติเรากำหนดเป็น 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
	}
	res.redirect('/me')
})
 
    การกำหนดข้างต้น มีผลให้ตัวแปร req.cookies.sid มีอายุอยู่ได้ 15 วัน นั่นก็คือ ข้อมูลการจดจำการล็อกอิน จะถูก
จดจำไว้เป็นเวลา 15 นั่นเอง 
 
    อย่างไรก็ตาม การใช้งาน session ข้างต้น เป็นการบันทึกข้อมูลไว้ที่ server ไว้ในหน่วยความจำ หรือที่เรียกว่า 
MemoryStore หากมีการ restart server ข้อมูลต่างๆ เหล่านี้ก็จะหายไป ถูกรีเซ็ตค่าใหม่หมด ดังนั้น การกำหนดให้จำ
ข้อมูลหน้าล็อกอิน ก็ไม่มีประโยชน์อะไร และแน่นอนว่าเราไม่ใช้วิธ๊การบันทึกข้อมูลไว้ใน MemoryStore แต่เราจะใช้งาน
ร่วมกับฐานข้อมูล โดยค่าต่างๆ ของ session จะถูกบันทึกไว้ในส่วนของ MongoDB
    สิ่งที่เราต้องทำคือกำหนด store property เข้าไป 
    โดยให้ติดตั้ง connect-mongodb-session module ด้วยคำสั่ง
 
npm install connect-mongodb-session --save
    จากนั้นให้เราสร้างไฟล์ชื่อ storeDB.js ไว้ในโฟลเดอร์ config และกำหนดไฟล์ดังนี้
 
    ไฟล์ storeDB.js [config/storeDB.js]
const session = require('express-session')
const MongoDBStore = require('connect-mongodb-session')(session)

const url = 'mongodb://localhost:27017' // กำหนด url สำหรับ MongoDB Server
const dbName = 'testdb' // กำหนดชื่อฐานข้อมูลที่จะใช้งาน

const store = new MongoDBStore({
    uri: url,
    databaseName: dbName,
    collection: 'mySession'
  },function(error) {
    if(error){
        console.log('Not connect to MongoDB')
    }
})
 
module.exports = store
 
    module เราทำการเชื่อมโยงใปยัง MongoDB และใช้งาน mySession collection ดังนั้น เราต้องสร้าง
collection ที่ชื่อ mySession สำหรับเก็บช้อมูล session ก่อน 
 
 
 
 
    จากนั้นเรียกใช้งานในไฟล์ app.js  ดังนี้
const store = require('./config/storeDb')  // เรียกใช้งาน module ที่เราสร้าง
    และในส่วนของการกำหนด session ก็กำหนดให้เรียกใช้ store เป็นดังนี้
app.use(session({
    name:'sid', // ถ้าไม่กำหนด ค่าเริ่มต้นเป็น 'connect.sid'
    secret: 'my ses secret',
    store:store, // ใช้งาน MongoDBStore 
    resave: true,
    saveUninitialized: true
}))
    ตอนนี้ตัวแปร session ของเราพร้อมที่จะบันทึกลงฐานข้อมูล MongoDB แทนการบันทึกไว้ใน MemoryStore แล้ว
ทดสอบหน้าล็อกอินอีกครั้ง ให้จดจำค่า 
 

 
 
    จากนั้นเราไปดูค่าในฐานข้อมูล 
 
 

 
 
    จะเห็นข้อมูล session ถูกบันทึกไว้ใน MongoDB เรียบร้อยแล้ว โดยมีค่า sid ที่เชื่อมและสัมพันธ์กับ _id ของข้อมูล
ในฐานข้อมูล นั่นหมายความว่า หากค่่า cookie sid ยังไม่หมดอายุ ก็จะมาดึงข้อมูล session ในฐานข้อมูลนี้ไปใช้งาน
และหากมีการแก้ไขค่า หรือกำหนดค่าให้กับตัวแปร session ค่าใหม่ ก็จะอัพเดทมาใน mySession Collection อัตโนมัติ
    - หากมีการลบตัวแปร session ด้วยคำสั่ง delete ค่า session นั้น ของ sid ปัจจุบัน ก็จะถูกลบออกจากฐานข้อมูล
    - หากเราทำการลบ seesion ทั้งหมดด้วยคำสั่ง req.session.destroy() ค่า session ทั้งหมดของ sid ปัจจุบัน
ก็จะถูกลบออกจากฐานข้อมูล 
    - หากตัวแปร cookie sid หมดอายุ และ server ส่ง session ID ค่าใหม่มาให้ ฐานข้อมูล ก็จะทำการเพิ่ม document
ใหม่ โดยมีค่า _id ใหม่เพิ่มเข้ามา ซึ่งจะสัมพันธ์กับ session ID  โดยข้อมูลของ session ID เดิมก็จะยังอยู่ในฐานข้อมูล
 
    เนื้อหาเกี่ยวกับ cookie และ session ใน Express ที่เราจะนำมาใช้งานร่วมกับระบบสมาชิก ในเบื้องต้น ก็จะประมาณนี้
อาจจะมีรายละเอียดปลีกย่อยอีกมากมาย ที่จะได้ศึกษาต่อตามลำดับ

 
    แต่ก่อนจบเนื้อหาส่วนนี้ จะเพิ่มเติม เกี่ยวกับระบบสมาชิกอีกหน่อย คือ
    ในระบบสมาชิก หากผู้ใช้ทำการล็อกอินเข้าระบบเรียบร้อยแล้ว สิ่งหนึ่งที่จะบอกได้ว่าผู้ใช้ได้ล็อกอินแล้ว ก็คือ session 
ที่เก็บสถานะการล็อกอิน ในที่นี้เราจะให้เป็น req.session.isLogined โดยจะมีค่าเท่ากับ true หากทำการล็อกอินอยู่
และเป็น false หรือไม่มีค่า session นี้ หากยังไม่ได้ล็อกอิน หรือ logout แล้ว 
    ตัวแปร session นี้ จะเป็นตัวช่วยกำหนดว่า หน้าใดๆ ที่จำเป็นต้องล็อกอิน เช่น ถ้าเป็นหน้าล็อกอิน หากมี isLogined เป็น
true อยู่แล้ว ก็ควรจะ redirect ไปหน้าสมาชิก dahsborad ที่ path:"/me"  เช่นเดียวกันกับหน้า สมัครสมาชิก
นอกจากนั้น ในหน้าสมาชิก dahsborad หากไม่มีค่า isLogined หรือ isLogined เป็น false ก็ควรจะทำการ redirect ไปยัง
หน้าสำหรับล็อกอินก่อน
    เราสมมติสถานะเมื่อผู้ใช้ล็อกอินสำเร็จ ในไฟล์ login.js บางส่วน โดยเพิ่ม
    req.session.isLogined = true เข้าไป
 
    ไฟล์ login.js บางส่วน [routes/login.js]
.post(validation(schema.login), (req, res, next) => { 
	// ผ่านการรวจสอบ ลิ้งค์ไปหน้า /me
	// สมมติเรากำหนดเป็น 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
	}
	req.session.isLogined = true
	res.redirect('/me')
})
 
    จากนั้นให้เราจะสร้าง middleware ฟังก์ชั่นสำหรับจัดการได้ดังนี้ 
    ให้เราสร้างไฟล์ชื่อ auth.js ในโฟลเดอร์ config
 
    ไฟล์ auth.js [config/auth.js]
// สร้าง middleware ฟังก์ชั่นสำหรับ redirect ไปหน้าต่างๆ ตามเงื่อนไข
// โดยมี parameter / ตัวคือ path และค่า status สถานะการล็อกอิน
const authorize = (path, status) =>{
    return ((req, res, next) => {
        if(req.session.isLogined === status || (!req.session.isLogined && status === false)){
            next() // ทำงานต่อใน path ปัจจุบัน           
        }else{
            res.cookie('flash_message', 'Please Login to access',{maxAge:3000})
            // redirect ไปยัง path ที่ต้องการตามที่ส่งค่ามา
            return res.redirect(path)      
        }
    })
}

module.exports = { authorize }
 
    สำหรับการเรียกใช้งาน เราจะกำหนดในไฟล์ app.js โดยเรียกใช้ module 
const { authorize } = require('./config/auth')
    และกำหนด middleware ฟังก์ชั่นเพื่อใช้งาน เป็นดังนี้
app.use('/', indexRouter)
app.use('/login', authorize('/me', false), loginRouter)
app.use('/register', authorize('/me', false), registerRouter)
app.use('/me', authorize('/login', true), dashboardRouter)
 
    ความหมายของการทำงานคือ
app.use('/login', authorize('/me', false), loginRouter)
// หน้าล็อกอิน เข้าหน้านี้ไม่ต้องมี session isLogined หรือถ้ามีต้องเป็นค่า false หากเป็นอื่น ให้ลิ้งค์ไปหน้าสมาชิก
app.use('/register', authorize('/me', false), registerRouter)
// หน้าสมัครสมาชิก เข้าหน้านี้ไม่ต้องมี session isLogined หรือถ้ามีต้องเป็นค่า false หากเป็นอื่น ให้ลิ้งค์ไปหน้าสมาชิก
app.use('/me', authorize('/login', true), dashboardRouter)
// หน้าสมาชิก เข้าหน้านี้ต้องมี session isLogined เป็น true หากเป็นอื่น ให้ลิ้งค์ไปหน้าล็อกอิน
 
    ไฟล์ 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 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
}))

 
// เรียกใช้งาน indexRouter
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}!`)
})
 
    และในส่วนของการ logout ก็กำหนดในไฟล์ dashboard.js เป็นดังนี้
 
    ไฟล์ dashboard.js [routes/dashboard.js]
const express = require('express')
const router = express.Router()

router.route('/')
    .get((req, res, next) => { 
        res.locals.pageData = {
            title:'Dashboard Page'
        }      
        res.render('pages/dashboard')    
    })

router.route('/logout')
    .get((req, res, next) => { 
        delete req.session.isLogined
        res.cookie('flash_message', 'Logout Complete!!',{maxAge:3000})
        res.redirect('/login')    
    })    

module.exports = router
 
    หวังว่าเนื้อหาเกี่ยวกับ cookie และ session ในบทความตอนนี้จะเป็นประโยชน์ และเป็นแนวทาง
สำหรับศึกษาในหัวข้อต่อๆ ไป
    เนื้อหาในตอนหน้าเราจะมาดูในเรื่องของการบันทึกข้อมูลการสมัครสมาชิกลงในฐานข้อมูล MongDB และ
การเข้ารหัส รหัสผ่านที่จะบันทึกลงฐานข้อมูล 


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



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









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






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

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

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

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



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




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











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