Session-Cookies

การเก็บสถานะของผู้ติดต่อกับเว็บเซอร์เวอร์ เพื่อให้รู้ใครเป็นใคร เนื่องจากผู้ติดต่อจำนวนมากกับเว็บเซิร์บเวอร์ การเก็บสถานะปัจจุบันของผู้ติดต่อแต่ละราย เพื่อให้ตอบสนองได้ตรงรายเดิม จะต้องมีอะไรบางอย่างจำจดผู้ติดต่อแต่ละรายได้ การทำให้จดจำแต่ละรายขณะที่ติดต่อได้จะต้องเก็บสถานะของผู้ติดต่อไว้ การเก็บสถานะเก็บได้สองทางคือเก็บข้อมูลสถานะไว้ที่เซิร์บเวอร์ กับเก็บไว้ที่ผู้ติดต่อ ซึ่งมีชื่อสถานะว่า Session สำหรับสถานะที่เซิร์บเวอร์ และชื่อว่า Cookies สำหรับการเก็บที่เว็บไคลเอ็นท์ เราจะเริ่มทำความเข้าใจกับ Session ก่อน และต่อด้วย Cookies

Session เป็นเก็บข้อมูลสถานะการใช้งานเว็บไซต์ของผู้ใช้ปัจจุบัน เพื่อแยกใครเป็นผู้ใช้งานในแต่ละเครื่อง ทำให้เซิร์บเวอร์เลือกตอบสนองให้กับผู้ใช้งานปัจจุบันได้อย่างถูกต้อง โดยค่าปริยายแล้ว Laravel เลือกเก็บ Session ในรูปแบบไฟล์ ดูได้ที่ storage/framework/sessions ซึ่งเป็นวิธีอย่างง่าย และเหมาะกับขั้นตอนการพัฒนา หากต้องการเก็บข้อมูล Session ในรูปแบบอื่นที่เหมาะสมกว่าก็ทำได้ เช่น Database, Redis หรือฐานข้อมูลเฉพาะอื่น การเก็บในรูปแบบอื่น เหมาะการกำหนดค่า Authentication

อย่างไรก็ตามคำสั่งการทำงานเก็บ Session ของรูปแบบไฟล์ ก็ยังคงเหมือนเดิม และใช้กับการเก็บข้อมูลแบบอื่น ๆ ได้ ในการศึกษานี้จะใช้รูปแบบการเก็บแบบไฟล์เพื่อความเข้าใจเบื้องต้น

การดำเนินการกับ Session

เราจะสมมุติเหตุการณ์การเข้าระบบ (login) โดยเมื่อเริ่มต้นสร้างเว็บแอปพลิเคชัน จะมีหน้า welcome ให้มา ด้วยแล้ว เมื่อเพิ่มการทำงานจะมีหน้า welcome แสดงเป็นหน้าเริ่มต้น แต่เพื่อทดสอบการใช้งาน session เราจะป้องกันการเข้าหน้า welcome ไม่ให้เปิดทำงานเป็นหน้าแรก โดยผู้ใช้งานจะต้องผ่านการ เข้าระบบก่อน โดยการใส่รหัสผ่าน ในหน้า login เมื่อเข้าระบบผ่าน ก็จะให้เก็บ Session ในชื่อ user ดั้งนั้นแล้ว หากผู้เข้าระบบมี session แล้ว ต่อไป ก็ไม่ต้องผ่านหน้า login อีกต่อไป

Session ในที่นี้คือที่เก็บค่าชั่วคราว โดยค่าปริยายกำหนดไว้ 120 นาที (2 ชั่วโมง) ถึงแม้จะปิดเบราเซอร์แล้วก็ตาม อย่างไรก็ตามเราสามารถเข้าไปแก้ไขจำนวนเวลาได้ที่ไฟล์ config/session.php


'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
		

เราจะเริ่มจาก การสร้างหน้าเว็บ login และสร้างเส้นทางเสียใหม่

Code 1. resources/views/login.blade.php

<!doctype html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link type="text/css" 
    href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <script src="js/jquery-3.6.3.min.js"></script>
</head>
<body>
   <div style="width:500px; margin:20px auto">
       <h2>Login</h2>
       <form method="POST" action="/login">
            @csrf
            <div class="mb-3 row">
                <label class="col-sm-2 col-form-label">Email</label>
                <div class="col-sm-10">
                    <input type="email" name="email" class="form-control">
                </div>
            </div>
            <div class="mb-3 row">
                <label class="col-sm-2 col-form-label">Password</label>                
                  <div class="col-sm-10">
                    <input type="password" name="password" 
                         class="form-control" autocomplete="off">
                </div>
            </div>
            <div>
                <button type="submit" class="btn btn-primary" 
                     style="float:right">Submit</button>
            </div>
        </form>
    </div>
</body>
</html>
        

สำหรับหน้า login นี้จะมี input รับอีเมล และ รหัสผ่าน แต่ละ input จะมีชื่อเป็น email และ pass ซึ่งชื่อนี้จะถูกส่งไปยังเซิร์บเวอร์ในรูปแบบอาร์เรย์แบบมีคีย์ เตามปกติจะใช้ ชื่อ $_POST['eamil'] แต่สำหรับ Laravel จะใช้ผ่าน Request ซึ่งต้องนำเข้าคลาสนี้ แล้วเรียกใช้ผ่านตัวแปรนี้ เช่น $request→input('email') ดังนั้น การใส่ชื่อในทุก input จึงสำคัญต่อการอ้างอิงตัวแปร

นอกจากนี้ ฟอร์มนี้ จะส่งข้อมูลแบบ POST และส่งไปยังเส้นทาง login ที่กำกับไว้บน

ดังนั้นจะต้องมีเส้นทาง /login เพื่อให้ส่งต่อไป LoginController ที่ทำงานกับฟังก์ชัน login( )

Code 2. resources/views/login.blade.php

use App\Http\Controllers\LoginController;
use Illuminate\Http\Request;

Route::get('/', function (Request $request) { 
    if(!$request->session()->exists('user')){
        return redirect('/login');
    }
    return view('welcome');
});

Route::get('/login', LoginController::class);
Route::post('/login', [LoginController::class, 'login']);
Route::fallback(function(){ return view('pageNotFound');});
        

สำหรับเส้นทางแรก (welcome) เป็นเส้นทางเริ่มต้น แต่เราได้กำหนดจุดประสงค์คือให้ผ่านหน้า login ก่อนเพื่อเก็บ session การตรวจสอบ session ใช้งานผ่าน $request->session()->exists() ซึ่งได้จากคลาส Request ถ้าไม่มี session ในชื่อ user ให้ไปยังหน้า login ก่อน หากมี session แล้วก็ไปยังหน้า welcome

จากการกำหนดเส้นทางของ login มีสองแบบ แบบแรกเป็นใช้การขอบริการด้วย get เพื่อแสดงหน้าเว็บ Loginและด้วยวิธี post เพื่อส่งคำขอเข้าระบบเมื่อกดปุ่ม Submit

ต่อมาสร้าง LoginController เพื่อจัดการการกับการเข้าระบบ

CLI 1.


> php artisan make:controller LoginController
        

จากเส้นทาง login เมื่อส่งมาแบบ get จะทำให้เป็นค่าปริยาย หากยังไม่ลืม คอนโทรลเลอร์ จะใช้งานผ่านฟังก์ชัน __invoke( ) และอีกเส้นทางที่ส่งมาแบบ post จะใช้งานผ่านฟังก์ชัน login()

ทั้งสองฟังก์ชันมีกลไกทำงานคือ

Code 3. app/Http/Controllers/LoginController.php

namespace App\Http\Controllers;
use Illuminate\Http\Request;

class LoginController extends Controller {
    public function __invoke(Request $request) {   
        //$request->session()->forget('user');
        if($request->session()->exists('user')){
            return redirect('/');
        }
        return view('login');
    }
    public function login(Request $request){        
        $email = $request->input('email') ;
        $password = $request->input('password');
        $request->session()->put('user', $email);  
        return redirect('/');      
    }
}
        

ทดสอบการทำงาน ว่าได้ผลเป็นอย่างไร ให้ไปยังหน้าแรกก่อน

127.0.0.1:8000

หากถูกต้อง จะได้ผลเป็นหน้า login เพราะยังไม่มี session เมื่ออยู่ในหน้า login แล้วให้กรอกข้อมูล (หรือกดส่งฟอร์มเลยก็ได้) แล้วกดส่งฟอร์ม ผลคือจะได้ไปยังหน้า welcome

ทดสอบอีกครั้ง ไปยังหน้า login

127.0.0.1:8000/login

หากถูกต้อง จะไม่สามารถเข้าหน้า login ได้เพราะมี session แล้ว ผลคือจะไปยังหน้า welcome

ตาราง 1 สรุปการใช้ session ผ่าน $request

ฟังก์ชัน ความหมาย
session( )->get('key') อ่าน sessionผ่านคีย์
session( )->all( ) อ่าน session ทั้งหมด
session( )->has('key') ตรวจสอบว่ามี session หรือไม่ ใช้กับค่าไม่เป็น null
session( )->exists('key') ตรวจสอบว่ามี session หรือไม่ ใช้กับค่า null ได้
session( )->push('key','value') เพิ่มค่า session
session( )->pull(‘key’, ‘value’) อ่านค่า session และลบทิ้ง
session( )->increment('count') เพิ่มค่า session ที่ละหนึ่งค่า หรือเพิ่มที่ละ 2 session( )→increment(‘count’, $incrementBy=2
session( )->decrement('count') ลดค่า session ที่ละหนึ่งค่า หรือลดที่ละ 2 session( )->decrement(‘count’, $decrementBy=2
session( )->flash('key', 'value') ใช้ session ชั่วคราว ใช้เพียงครั้งต่อไปก็จะลบ
session( )->forget('key')
session( )->flush( )
ลบ session ค่าและถ้าต้องการลบที่หลาย session session( )->forget(['key1', key2']) แล้วต่อด้วย flush( )
session( )->regenerate( ) สร้าง session ขึ้นเองอัตโนมัติ เพื่อไม่ให้ซ้ำกับใครเป็นค่าที่เดาได้ยาก ป้องกันการปลอม session

อ่านเพิ่มเติมเรื่อง Session: https://laravel.com/docs/8.x/session

นอกจากการสร้าง session จากออบเจ็กต์ Request แล้ว ยังสามารถสร้าง session ในระดับ Global ดังการอ่านและเขียนคือ


$value =  session('key');
session([‘key’ => ‘value’]);
		

Cookies

Cookies เป็นสถานะที่ฝั่งไคลเอ็นท์ ไว้ได้ยาวนานกว่า Session เพราะ Session จะหายไปเมื่อถึงเวลาหนึ่ง เช่น 2 ชั่วโมง แต่สำหรับ Cookies แล้วสามารถเป็นได้เป็นปี ข ึ้นอยู่การกำหนดเวลาให้ยาวนานเท่าใด(กี่วินาที)

โครงสร้างการเก็บ Cookies: [Name, Value, Domain, Path, Expires/Max-Age, Size, HttpOnly, Secure, SameStie, SameParty, Prioiry]

ดูความหมายได้ที่ https://developer.chrome.com/docs/devtools/storage/cookies/?utm_source=devtools

รูป 1 หน้าต่างค่าของ Cookies บน Google Chrome (More Tools/Developer Tools/Application/Cookies

เราทดสอบการเขียน Cookies ได้ด้วยคำสั่ง response( )->cokies($cookies) อ่าน Cookies ผ่าน Request และถ้าต้องการลบ Cookies ใช้การลบตามชื่อของ Cookies


//set cookies
return response('Hello World')->cookie(
    'name', 'value', $minutes, $path, $domain, $secure, $httpOnly
);

//read cookies
$value = $request→cookie('name');

//delete cookies
response()->withoutCookie('localhost')
		
Code 4. routes/web.php

use Illuminate\Http\Request;

Route::post('/login', function (Request $request) {
    $cookie = cookie('localhost', 'Hello world', 10);//10 minutes
    return response($request->cookie('localhost'))->cookie($cookie);
    //return 'Hello world
});