กำหนด Authentication จำกัดการเข้าถึง API ใน CodeIgniter 4

เขียนเมื่อ 3 ปีก่อน โดย Ninenik Narkdee
codeigniter codeigniter 4 restful api authentication jwt php

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ codeigniter codeigniter 4 restful api authentication jwt php

ดูแล้ว 8,180 ครั้ง


จากตอนที่แล้ว เราได้เห็นตัวอย่างการใช้งาน RESTful API
ใน CI4 ไปแล้ว และได้ทิ้งท้ายเกี่ยวกับหัวข้อต่อไป คือการ
จำกัดการเข้าถึง API ที่เราสร้าง ทบทวนเนื้อหาตอนที่แล้วได้ที่
    ตัวอย่างการประยุกต์ใช้งาน RESTful API ใน CodeIgniter 4 http://niik.in/1014 
 
ในเนื้อหานี้ จะพูดถึง การกำหนด Authentication จำกัดการเข้าถึง API โดยใช้ Basic 
Authentication และ ใช้ JWT (JSON Web Token)  จะใช้เนื้อหาและโค้ดของตอนที่แล้ว
ประกอบการอธิบาย นั่นคือ เรามี API อยู่แล้ว และต้องการจำกัดการเข้าถึง โดยไม่ใช่ทุกคน
ที่จะสามารถเข้าถึง API นี้ได้
    เมื่อมีการตรวจสอบการ Request สิ่งที่เราจะต้องเรียกใช้งานก็คือ Controller Filter ซึ่งจะทำ
หน้าที่ดักจับ request ที่ส่งเข้ามา และให้ทำงานหรือจัดการบางอย่าง การที่จะเข้าไปถึง Resource
Controller หลักของ API ทบทวนการใช้งาน Filter ได้ที่บทความ
    รู้จักกับ Controller และ Controller Filter ใน CodeIgniter 4 http://niik.in/997 
 
 
 

สร้าง Controller Filter

    เริ่มต้นให้เราสร้าง Filter สำหรับใช้งานก่อน ในที่จะใช้ชื่อเป็น AuthApi.php กำหนดโค้ดไฟล์เริ่มต้น
ตามตัวอย่างด้านล่าง
 
    app/Filters/AuthApi.php
 
<?php namespace App\Filters;
 
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;

class AuthApi implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        echo "test";
    }
 
    //--------------------------------------------------------------------
 
    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // Do something here
    }
}
 
    เนื่องจากเราจะจัดการ Request ก่อนที่จะเข้าไปใน Controller หลัก ดังนั้น method ที่เราจะใช้งาน
จะเป็น before() เบื้องต้นเราใช้คำสั่ง echo แสดงข้อมูลทดสอบก่อน
 
    หลังจากสร้าง Filter แล้ว ต่อไปเราก็ต้องตั้งค่าการใช้งานในไฟล์ Filters.php
 
    app/Config/Filters.php
 
public $aliases = [
	'csrf'     => \CodeIgniter\Filters\CSRF::class,
	'toolbar'  => \CodeIgniter\Filters\DebugToolbar::class,
	'honeypot' => \CodeIgniter\Filters\Honeypot::class,
	'authApi' => \App\Filters\AuthApi::class,
];
 
    สังเกตส่วนสุดท้าย ที่เราเพิ่มเข้าไปคือชื่อเรียกที่จะใช้งาน และ path ของ Filter Class เราจะเรียกใช้งาน
Filter ด้วยชื่อที่กำหนดเองว่า 'authApi'
     สำหรับการกำหนดใช้งาน เราสามารถกำหนดใช้งานในไฟล์ Filters.php นี้ได้ตามเนื้อหา http://niik.in/997
แต่ในที่นี้เราจะไปกำหนดในส่วนของ Routes แทน 
 
    app/Config/Routes.php
 
$routes->resource('api/users',['controller' =>'Api\Users','filter' => 'authApi']);
 
    สังเกตว่าเราเพิ่ม option ที่ชื่อว่า filter และเรียกใช้งานชื่อ filter ตามค่าที่เราได้กำหนดไว้แล้ว
 
    ทดสอบเรียกใช้งาน Users API ดูผลลัพธ์ทีได้ดังรูปด้านล่าง
 
 

 
 
    ข้อความ "test" ถูกแสดงอยู่ด้านบนของข้อมูลจาก API นั่นแสดงว่า ตัว Filter เราทำงานถูกต้องแล้ว
พร้อมสำหรับการกำหนดการใช้งานเพิ่มเติม
 
 
 

จำกัดการเข้าถึง API

    ตอนนี้เรารู้แล้วว่า เมื่อเรียกไปยัง API ตัว Filter ก็จะเรียกใช้งาน before() method ก่อน ดังนั้น ถ้าเราจะ
กำหนด หรือจำกัดสิทธิ์การเข้าถึง API ก็จะจัดการในส่วนนี้ แต่เนื่องจากใน before method จะมีแค่ Request
instance ให้ใช้งาน การจะตอบกลับไปยังผู้ใช้ เราจำเป็นต้องใช้งาน Response instance ซึ่งสามารถทำได้
 
$response = service('response');
 
    เมื่อเราได้ Response instance แล้ว เราสามารถกำหนดรูปแบบข้อความตอบกลับไปยัง Client กรณีไม่
ผ่านเงื่อนไขการตรวจสอบการเข้าใช้งาน หรือถูกจำกัดสิทธิ์ในรูปแบบดังนี้ได้
 
    app/Filters/AuthApi.php
 
public function before(RequestInterface $request, $arguments = null)
{
	$response = service('response');
	$data = [
		"status" => 401,
		"error" => 401,
		"messages" => [
			"error"  => "Unauthorized"
		]
	];
	return $response->setStatusCode(401)->setJSON($data);					
}
 
    ดูตัวอย่างผลลัพธ์ เมื่อทดสอบเรียก API
 
 

 
 
    จะเห็นว่าตอนนี้ เราสามารถจำกัดการเข้าถึง API ได้แล้ว  และแน่นอนว่า เราคงไม่จำกัดสิทธิ์โดยไม่มี
เงื่อนไข หัวข้อต่อไป เราจะมาดูเงื่อนไขการจำกัดสิทธิ์ ซึ่งจะแนะนำใน 2 รูปแบบ คือ Basic Authentication
และ JWT (JSON Web Token) 
 
 
 

การกำหนด Basic Authentication

    ถึงแม้ว่าการใช้งานในรูปแบบนี้ จะไม่เหมาะสมในเรื่องของความปลอดภัย สำหรับข้อมูลสำคัญๆ แล้ว แต่
มันก็ยังเป็นรูปแบบที่เหมาะกับการใช้งาน API ส่ว่นตัวหรือการใช้งานร่วมกับ Bot ที่เรากำหนดเองได้ และถ้า
เรานำมาประยุกต์ใช้งานกับ Token เช่น Token มีอายุการใช้งาน และเมื่อหมดอายุ ก็จะไม่สามารถเรียกใช้ได้
แบบนี้เป็นต้น ดังนั้น จึงขอแนะนำรูปแบบนี้เป็นแนวทาง
 
    app/Filters/AuthApi.php
 
public function before(RequestInterface $request, $arguments = null)
{
	$valid_passwords = [
		"admin" => "test"
	];
	$valid_users = array_keys($valid_passwords);
	$user = $request->getServer('PHP_AUTH_USER');
	$pass = $request->getServer('PHP_AUTH_PW');
	$validated = (in_array($user, $valid_users)) && ($pass == $valid_passwords[$user]);
	if (!$validated) {		
		$response = service('response');
		$data = [
			"status" => 401,
			"error" => 401,
			"messages" => [
				"error"  => "Unauthorized"
			]
		];
		return $response->setStatusCode(401)->setJSON($data);		
	}
}
 
    รูปแบบข้างต้น เรากำหนด username โดยใช้ key ของ ตัวแปร $valid_passwords และกำหนด 
password โดยใช้ value ของตัวแปร $valid_passwords ถ้าค่า จาก $_SERVER['PHP_AUTH_USER']
และ $_SERVER['PHP_AUTH_PW'] ซึ่งใน CI จะใช้คำสั่ง getServer() เป็นการเรียกใช้งานตัวแปร
$_SERVER  เมื่อทั้งสองค่าที่ส่งมา ตรงตามเงื่อนไข เราก็สามารถเรียกใช้งาน API ได้
    ซึ่งตอนนี้ ถ้าเราเรียกใช้งาน ก็จะยังไม่สามารถเข้าใช้งาน API ได้ ดังนั้น เรามาดูรูปแบบการส่งค่า
 
     สำหรับการใช้งาน Authentication นั้น เราจะต้องส่งค่า header ที่มีชื่อว่า Authorization เข้ามาด้วย
ด้วยค่าสำหรับ Basic Authentication จะอยู่ในรูปแบบ Basic xxx  เช่น ถ้าค่า username เป็น admin
และ password เป็น test ค่า xxx จะเท่ากับ base64_encode("admin:test") หรือถ้าใช้คำสั่ง PHP
ก็จะประมาณนี้
 
echo base64_encode("admin:test");
// YWRtaW46dGVzdA==
 
    เรารู้อยู่แล้วว่า base64 สามารถแปลงค่ากลับด้วยคำสั่ง base64_decode() ดังนั้น เราไม่ควรใช้ค่า
ที่เป็น username หรือ password สำหรับกำหนดในส่วนนี้ ให้เราใช้เป็น Token เช่น อาจจะกำหนด
เป็นรูปแบบดังนี้แทน
 
echo base64_encode("admin@localhost.com:6h4hi3KpMvvFJYPzhUtgMTUQ8qk96fQB");
 
    คือใช้เป็น email กับ token เป็นต้น
    ในที่นี้เราจะจำลองใช้งาน ดังนั้นก็จะใช้เป็น admin กับ test ก่อน ก็จะได้ค่า xxx เป็น 
YWRtaW46dGVzdA==  เราเอาค่านี้ไปใส่ใน header และเรียกใช้งานตามรูป 
 
 

 
 
    จะเห็นว่า ถ้าค่าถูกต้อง ก็จะสามารถเข้าถึง API ได้ ตัวแปร SERVER จะแปลงค่าจาก header มา
ไว้ในตัวแปร $_SERVER['PHP_AUTH_USER'] และ $_SERVER['PHP_AUTH_PW'] ตามลำดับ
และค่าก็ถูกถอดรหัสจาก base64 เรียบร้อย เมื่อเราเอาค่าไปเทียบ กับค่าในตัวแปร หากตรงกัน เราก็
สามารถใช้งาน API ได้ แต่ถ้าไม่ตรงกับที่กำหนด ก็จะขึ้นแจ้งเป็นไม่ผ่านตรวจสอบ ถูกจำกัดสิทธิ์ เป็นต้น
    ในที่นี้เราแค่ให้แนวทางการใช้งาน เราสามารถประยุกต์เช่น ค่า token เราอาจจะเก็บในฐานข้อมูล อาจจะ
มีเงื่อนไขของวันหมดอายุ หรืออื่นๆ แล้วแต่ประยุกต์ เพื่อให้การเข้าถึง API มีความยากยิ่งขึ้น
    อย่างไรก็ตาม วิธีนี้ก็ถือว่า ไม่แนะนำสำหรับการใช้งานที่เน้นเรื่องความปลอดภัย 
 
 
 

การกำหนด JWT (JSON Web Token)

    แนะนำให้เราทำความเข้าใจ JWT เพิ่มเติม โดยสามารถค้นหาใน google หรือจะเข้าไปดูเกี่ยวกับ
JWT ได้ที่ลิ้งค์นี้ https://jwt.io/introduction/
    ในการใช้งาน JWT เราจำเป็นจะต้องทำการติดตั้ง package เพิ่มเติม ให้เราไปที่ root ของ project
แลัวใช้คำสั่ง ติดตั้ง package firebase/php-jwt ดังนี้
 
composer require firebase/php-jwt
 
    รูปแบบคำสั่งการใช้งาน เพื่อที่จะได้ค่า Token จะมีรูปแบบการใช้งานง่ายดังนี้
 
<?php
use \Firebase\JWT\JWT;

$key = "secret_key";
$payload =[
    "iss" => "http://example.org",  // ผู้ออก
    "aud" => "http://example.com", // ผู้รับ
    "iat" => 1356999524, // เวลาออก timestamp
    "nbf" => 1357000000 // เวลาห้าม process ก่อน	
];

$jwt = JWT::encode($payload, $key); // ได้ค่า token
?>
 
    ในขั้นตอนการสร้าง Token เราจะต้องมี secret_key ใช้ค่าเข้ารหัสอะไรก็ได้ ตามต้องการ
และมีส่วนของการกำหนด payload หรือข้อมูลที่สามารถเปิดเผย หรือแสดงได้ โดยสามารถกำหนด
ค่าต่างๆ เพิ่มเติมตามนี้ https://tools.ietf.org/html/rfc7519#section-4.1    
 
 
    ส่วนใหญ่จะใช้ค่าเป็นประมาณนี้
 
$payload =[
    "iss" => "http://example.org",  // ผู้ออก
    "sub" => "user id", // หรือข้อมูล ที่เราอยากใช้งาน
    "iat" => 1356999524, // เวลาออก timestamp
    "exp" => 1356987454 // เวลาหมดอายุ Token
];
 
    ตัว $key จะถูกใช้สำหรับสร้าง Token และ ใช้สำหรับ ถอดรหัสหรือตรวจสอบความถูกต้องของ Token
ดังนั้น จึงเป็นค่าเดียวกัน
    การกำหนดในส่วนของการสร้าง Token จะไม่ขอลงลายระเอียด แนวทางก็เช่น เมื่อผู้ใช้ล็อกอินเข้าระบบ
เราก็ทำการเรียกใช้คำสั่งหรือรูปแบบข้างต้นสร้าง Token ไว้ใช้งาน ในที่นี้เราจะสมมติการสร้าง token ด้วย
ข้อมูลประมาณนี้
 
<?php
use \Firebase\JWT\JWT;

$key = "secret_key";
$payload =[
    "iss" => "https://www.mysslweb.com",  // ผู้ออก
    "sub" => "admin@mysslweb.com", // ข้อมูล
    "iat" => time(), // เวลาออก timestamp
    "exp" => time()+300 // เวลาหมดอายุ 5 นาที
];
$jwt = JWT::encode($payload, $key); // ได้ค่า token		
echo $jwt; 
?>
 
    ตอนนี้เราได้ค่า Token มาใช้งานแล้ว และจะส่งไปยัง API พร้อมกับ header ในรูปแบบ กำหนด
ค่า Authorization ในรูปแบบ
 
Bearer xxx
 
    โดย xxx คือค่า Token จากตัวแปร $jwt
 
    ต่อไปกำหนดรูปแบบการตรวจสอบ Token ที่ส่งมาใช้งาน ใน Filter เราจะได้เป็นดังนี้
 
    app/Filters/AuthApi.php
 
<?php namespace App\Filters;
 
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;

use \Firebase\JWT\JWT;
use Firebase\JWT\ExpiredException;
use Exception;

class AuthApi implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
		$key = "secret_key";
		list(,$jwt) = explode(" ",$request->getHeaderLine('Authorization'));				
		try {
			$decoded = JWT::decode($jwt, $key, array('HS256'));
		} catch (ExpiredException $e) {
			$response = service('response');
			$data = [
				"status" => 401,
				"error" => 401,
				"messages" => [
					"error"  => "Token expired"
				]
			];
			return $response->setStatusCode(401)->setJSON($data);				
		} catch (Exception $e){
			$response = service('response');
			$data = [
				"status" => 401,
				"error" => 401,
				"messages" => [
					"error"  => "Unauthorized"
				]
			];
			return $response->setStatusCode(401)->setJSON($data);		
		}
    }
 
    //--------------------------------------------------------------------
 
    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // Do something here
    }
}
 
    เราใช้ตัวแปร $key เป็นค่าเดียวกันกับที่ใช้สร้าง Token เมื่อมีการส่ง header ค่า Authorization เข้ามา 
เราทำการแยกค่าด้วยช่องว่าง ระหว่างค่า token กับคำว่า 'Bearer' เราจะได้ค่า Token ในตัวแปร $jwt จาก
นั้นเราใช้งานจัดการ Error ร่วมกับการตรวจสอบข้อมูลของ Token ด้วยคำสั่ง JWT::decode() หากข้อมูล
Token ถูกต้อง เราก็จะสามารถเข้าถึง API ได้ แต่ถ้าไม่ผ่าน เรากำหนดการจัดการกับ Error ไว้สองส่วนคือ
Token หมดอายุ กับ Token ไม่ถูกต้อง 
    สังเกตส่วนบนของไฟล์ เราเรียกใช้งาน 3 class เพิ่มเข้ามาคือ JWT, ExpiredException และ Exception
เนื่องจากในตัวอย่าง เรากำหนดวันหมดอายุทดสอบไว้ที่ 5 นาที และมันก็หมดอายุแล้ว เรามาลองทดสอบ
เรียก API ก็จะได้ผลลัพธ์ดังนี้
 
 

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

 
 
    เท่านี้เราก็สามารถกำหนด Authentication จำกัดการเข้าถึง API ในรูปแบบที่ปลอดภัยยิ่งขึ้น และวิธีใช้งาน
ก็ไม่ยุ่งยากซับซ้อน
 
    สำหรับการใช้งาน API สำหรับ Website เรายังสามารถกำหนด IP หรือใช้งาน session เพื่อจำกัดการเข้าถึง
API ได้อีกด้วย วิธีการก็แค่ตรวจสอบเพิ่มเติมว่า IP ที่เรียกมาอยู่ใน Blacklist หรือ Whitelist รายการที่เรา
กำหนดหรือจัดเตรียมไว้หรือไม่ แล้วทำเงื่อนไขตามต้องการ เป็นต้น
    นอกจากวิธีข้างต้นที่แนะนำไปแล้ว ยังมีอีกวิธี คือการใช้งานในรูปแบบ OAuth 2.0 ซึ่งจะมีวิธีการใช้งาน
ที่ซับซ้อนขึ้นมาอีก ซึ่งขึ้นกับความสำคัญของข้อมูล ความปลอดภัย และการจัดการกับผู้ใช้งานจำนวนมากๆ
 
    เนื้อหาเกี่ยวกับการจัดการ RESTful API เบื้องต้นใน CI4 ก็จะมีประมาณนี้ เนื้อหาตอนหน้าจะมาแนะนำ
การ Request ไปยัง API โดยใช้ CURLRequest Class ใน CI4 รอติดตาม


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



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









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






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

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

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

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



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




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





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


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







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