Technology

Go Tutorial Series EP.3: ทำ Authorization ด้วย JWT และ Middlewareในภาษา Go

จากตอน EP ที่แล้วเราได้ทำการเก็บ Data ของ API ด้วย Gorm Library ลงใน SQLite Database เป็นทีเรียบร้อยแล้ว

ในวันนี้เราจะมาทำการทำ Authorization ด้วย JSON Web Token หรือว่า JWT และใช้ Middleware ใน Gin Framework บนภาษา Go กัน

สำหรับใครที่ยังไม่เคยเขียน API ด้วยภาษา Go หรือไม่เคยใช้ Gin Framework มาก่อน ผมแนะนำให้ลองอ่านบทความ EP. 1 ของเราที่นี่ก่อนได้เลยครับ

แต่ถ้าพร้อมแล้ว เรามาเริ่มจากการทำ Authorization แบบง่ายๆ กันก่อนเลยดีกว่า โดยเริ่มจากใน listBooksHandler ของเราเช่นเดิม

เราได้เพิ่ม Logic เกี่ยวกับการทำ Authorization เข้ามาในบรรทัดที่ 2 ถึงบรรทัดที่ 9 โดยเริ่มจากดึง Token ออกมาจาก Header ที่ชื่อ Authorization ใน HTTP Request ด้วยคำสั่ง c.Request.Header.Get("Authorization")

ต่อมาเราทำการตัดมาเฉพาะส่วนที่เป็น token จริงๆ โดยการตัดคำว่า Bearer ออก (เนื่องจากว่าหน้าตาของ Token จะเป็น Bearer MY_TOKEN_STRING )

หลังจากที่เราได้ Token มาแล้ว เราก็ทำการเช็ค token ว่าเป็น token ที่ถูกต้องหรือไม่ด้วยฟังก์ชัน validateToken ซึ่งจะเป็นฟังก์ชันที่เราจะมา implement กันต่อจากนี้ ซึ่งเมื่อฟังก์ชัน validateToken ของเรามี error เกิดขึ้น เราก็จะทำการส่ง status 401 Unauthorized กลับไปให้กับ client ของเรา

ต่อจากนั้นเราลองมา implement ฟังก์ชัน validateToken แบบง่ายๆ กันดูก่อน

โค้ดข้างบนคือการเช็ค token แบบง่ายๆ เลยก็คือแค่ส่งเข้ามาก็ถือว่า valid ไม่ว่า token จะเป็นอะไรก็ตาม

และเราก็มาลองทดสอบ Request กันแบบง่ายๆ ด้วย REST Client ของเรากันเหมือนเดิม โดยที่เราจะยังไม่ได้ใส่ Authorization Header เข้าไปก่อน

GET http://localhost:8080/books

ผลลัพธ์ที่เราได้ก็จะเป็นแบบนี้

ซึ่งผลลัพธ์ก็จะเป็นตามที่คาดนั่นก็คือเราจะได้ status 401 Unauthorized เพราะว่าเราไม่ได้ส่ง Token ให้กับ Server ของเรา

ทีนี้เรามาลองยิง request กันอีกรอบ แต่คราวนี้เราจะส่ง Token เข้าไปด้วยแบบนี้

GET http://localhost:8080/books
Authorization: Bearer abcdefghijk

ผลลัพธ์ที่ได้ก็จะเป็นแบบนี้

ซึ่งเราก็จะสามารถเข้าถึงข้อมูลของ API ได้ตามปกติเหมือนก่อนหน้านี้ที่เราเคยทดสอบ API ไว้ใน EP ก่อนๆ

ทีนี้ถ้าสมมติว่าเราอยากจะนำ Logic นี้ไปใช้กับ route อื่นๆ อย่างเช่น POST และ DELETE ด้วย เราอาจจะสามารถทำได้แบบนี้

ซึ่งสังเกตว่า เราจะต้องมีโค้ดการเช็ค Token อยู่ตามทุก Route ของเราเลย ซึ่งทำให้มี Duplicated Code จำนวนมากตามจำนวน Handler ที่เรามีเลย ซึ่งจะทำให้มีปัญหาการ Maintain โค้ดต่อในอนาคต เพราะถ้าหากต้องการที่จะแก้ Logic Authorization อันนี้ เราก็จะต้องมาไล่แก้กับทุกๆ Handler

ซึ่งปัญหานี้เราสามารถแก้ได้ด้วยการใช้ Middleware

รู้จักกับ Middleware

ตัว Middleware ใน Gin Framework นั้นถูกสร้างมาเพื่อให้มีการใส่โค้ดบางอย่างสำหรับหลายๆ route ที่เราต้องการได้ เพื่อแก้ปัญหาความซ้ำซ้อนของโค้ดอย่างแบบเมื่อกี้นี้เราเจอกัน

ทีนี้เราลองมา Implement ตัว Middleware ของเรากันดูเลยดีกว่า

โดยเริ่มจากการแยก Logic ของการเช็ค Authorization Token มาเป็น Middleware Function ก่อนแบบนี้

หลังจากนั้นก็ทำการสร้าง route group ใน main function และเปลี่ยนการ register route เดิมมาใช้ route group แทนแบบนี้ (บรรทัดที่ 14 ถึง 18)

ทีนี้เราก็สามารถที่จะทำ Logic การ Authorization ไว้ในที่เดียวใน authorizationMiddleware เรียบร้อย ซึ่งทำให้เราสามารถ Maintain โค้ดของเราได้ง่ายขึ้นมากๆ เพราะ Logic จะถูกเก็บไว้อยู่ในที่เดียวใน Middleware

สร้าง Route เพื่อ Generate Token

โดยปกติแล้ว การทำ Authorization นั้น จะต้องมี route เพื่อที่จะ Generate Token ให้กับ Client เพื่อที่ให้ Client สามารถ Access เข้า API ต่างๆ ซึ่ง route นั้นมักจะเป็น route POST /login (ซึ่งใน Tutorial นี้เราจะทำ Route นี้แบบง่ายๆ โดยที่จะไม่ได้มีการเช็ค Logic เรื่องการ Login แต่จะเน้นเรื่อง Middleware และ JWT เป็นหลัก)

โดยเรามาเริ่มการสร้าง Route POST /login

และก็ทำการ Register Route Login บน router แบบปกติที่เราทำกันใน main function (บรรทัดที่ 14)

และเราก็ทำการเปลี่ยน Logic ในฟังก์ชัน validateToken ของเราเป็นแบบนี้แทน เพื่อให้สอดคล้องกันกับ loginHandler ที่เราสร้างขึ้นเมื่อกี้นี้

และเราลองทดสอบกันเช่นเดิม โดยเริ่มจาก Login Route เพื่อเอา Token มาก่อน

POST http://localhost:8080/login

ซึ่งเราก็จะได้ Token ออกมาแบบนี้

และนำ Token ที่ได้มาใช้ในการเข้าถึง API โดยใส่เข้าไปใน Authorization Header

GET http://localhost:8080/books
Authorization: Bearer ACCESS_TOKEN

ซึ่งถ้าเราลองเปลี่ยน Token เป็นแบบอื่นๆ ดู

GET http://localhost:8080/books
Authorization: Bearer ANOTHER_TOKEN

ก็จะเป็น 401 Unauthorized ตามที่เราคาดไว้

แต่ว่าการใช้ Token แบบง่ายๆ ที่เราทำกันนี้ จะมีจุดอ่อนเรื่องของการที่อาจจะมี Third Party ที่มีเจตนาไม่ดี เข้ามาปลอมแปลงตัว Token มาเพื่อที่จะโจมตีระบบของเราได้ และ Server ก็จะถูกหลอกได้ง่ายๆ เพราะ Server ไม่รู้ว่า Token นั้นเป็น Token จริงๆ ที่ Server นี้เป็นคนสร้างหรือไม่ เพราะไม่ได้มีหลักฐานอะไรเป็นตัวบอกว่า Token นี้ถูกสร้างโดย Server นี้

ซึ่งถ้าเรามีสิ่งที่เป็นเหมือนลายเซ็นของ Server แล้วเราสามารถ “เซ็น” ลายเซ็นเหล่านี้ลงบน Token นี้ได้เพื่อที่เราจะสามารถรู้ได้ว่า Token นี้เป็นของ Server เราจริงๆ ก็น่าจะดี และหนึ่งในวิธีการทำสิ่งนี้ก็คือการทำ JWT หรือ JSON Web Token นั่นเอง

ใช้ JWT ในการทำ Authorization Token

JSON Web Token หรือ JWT เป็นหนึ่งรูปแบบของ Token ที่เรานำมาใช้ในการเก็บข้อมูลในการส่งเพื่อทำการ Authentication และ Authorization
(สำหรับคนที่ยังไม่รู้จักว่า JWT คืออะไร ใช้งานยังไง ผมแนะนำให้ศึกษาเพิ่มเติมเกี่ยวกับ JWT ได้ที่นี่เลยครับ https://jwt.io/introduction)

ซึ่งในภาษา Go เองก็มี library เกี่ยวกับการทำ JWT อยู่ใน Repository https://github.com/golang-jwt/jwt

โดยเราจะแก้ loginHandler ของเราให้ Generate ตัว JWT Token เป็นแบบนี้

ซึ่งในโค้ดนี้ เราจะสร้าง token ด้วยคำสั่ง jwt.NewWithClaims และทำการระบุ signing algorithm ของเราเข้าไป พร้อมกับการใส่ Claim เข้าไป ซึ่งในทีนี้เราจะใส่เพียงแค่เวลาที่หมดอายุของ Token นี้

หลังจากนั้นเราก็จะทำการ “เซ็น” ด้วยลายเซ็นหรือ Signature ของ Server เรา (ซึ่ง Signature นี้จะต้องเก็บไว้เป็นความลับ ไม่ควรมีใครรู้) ซึ่งในทีนี้เราจะใช้เป็น string ("MySignature") ที่แปลงเป็น []byte

และถ้าไม่ได้มี error อะไรจากการที่ทำการ sign signature นั้น เราก็สามารถที่จะคืน token ของเรากลับไปได้ตามปกติ

ทีนี้เราลองมาเทส Route Login เพื่อดู Token ของเรากันใหม่ดีกว่า

POST http://localhost:8080/login

ผลลัพธ์ที่ได้ก็จะเป็นแบบนี้

เราก็จะได้ JSON Web Token ที่ถูก Generate ออกมาเรียบร้อย

ซึ่งถ้าเรานำ Token อันนี้ไปลองใส่ดูใน https://jwt.io/ ดูก็จะสามารถเห็นข้อมูลต่างๆ ที่เราสร้างมาไว้กันเมื่อกี้นี้ได้

ขั้นตอนต่อไปคือเราจะไปแก้ Logic การตรวจสอบ token ที่ฟังก์ชัน validateToken

เราสามารถอ่าน token ได้ด้วยคำสั่ง jwt.Parse โดยเราใส่ token เข้าไป และ function ที่เอาไว้ return signature ที่เราทำการ sign ตัว JWT ไว้ ซึ่งนั้นก็คือ []byte("MySignature") นั่นเอง

นอกจากนี้ภายใน function เราก็มีการเช็คว่า signing method นั้นตรงกับที่เราได้ทำการ signed ไว้ตอนแรกหรือไม่ ซึ่งถ้าไม่ตรงเราก็จะทำการคืนค่า error กลับไป

เมื่อเสร็จเรียบร้อยเราก็ทำการทดสอบ API ของเราด้วย JWT ที่เรา Generate ออกมากันเมื่อกี้นี้

GET http://localhost:8080/books
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDAxNjU0MTZ9.fInd5mOn7nd92-fV30oLTatx13Cun3TeceiY6r0A2E8

ผลลัพธ์ที่เราได้ก็จะเป็นแบบนี้

ซึ่งตรงนี้ถ้าหากว่าใครเกิดเจอว่าขึ้นเป็น 401 Unauthorized ก็อาจจะเป็นเพราะว่าตัว JWT ที่เรา Generate ขึ้นมาในตอนแรกนั้น ได้หมดอายุหรือ expire ไปแล้ว ซึ่งเราก็สามารถแก้ไขได้ง่ายๆ โดยการ Generate Token อันใหม่ที่ POST /login เช่นเดิม

Code ฉบับเต็ม

เสร็จเรียบร้อย! เท่านี้เราก็สามารถใช้ JWT คู่กับ Middleware ใน Gin Framework ในการทำ Authorization ตัว API ของเราได้เรียบร้อยแล้ว

You may also like

Data

Apache Airflow คืออะไร แล้วทำไมองค์กรชั้นนำส่วนใหญ่ถึงเลือกใช้

Apache Airflow คือ 1 ใน Workflow Management ที่ได้รับความนิยม และองค์กรชั้นนำระดับโลกหลายๆ องค์กรเลือกใช้ โดยเฉพาะอย่างยิ่งในการสร้าง Data Pipelines เพื่อจัดการกับข้อมูลจำนวนมหาศาล ส่วนหนึ่งเพราะองค์กรต่าง ...
8 เหตุผลทำไมควรเขียน Scala
Technology

8 เหตุผลที่ Dev ควรลองเขียนภาษา Scala ตั้งแต่ตอนนี้

เราเคยเกริ่นถึงภาษา Scala ไปบ้างแล้วจากบทความ ภาษา Scala มีจุดเด่นอะไร? ทำไมกำลังมาแรงในสาย Developer และ Data Engineer แต่ในกลุ่มนักพัฒนาหลายคนยังสงสัยว่าภาษา Scala มีความเหมาะไปใช้ในงานแบบไหน? ทำไมถึงต้องหันมาศึกษา ...

More in:Technology

ภาษา Scala คืออะไร Technology

ภาษา Scala มีจุดเด่นอะไร? ทำไมกำลังมาแรงในสาย Developer และ Data Engineer

Scala คือ ภาษา Programming ที่กำลังมาแรงและเริ่มมีความนิยมใช้กันขึ้นเรื่อย ๆ จุดเริ่มต้นภาษา Scala เรียกได้ว่าเป็นลูกอีกคนหนึ่งของภาษา Java เช่นเดียวกับภาษา Kotlin ที่พัฒนาต่อยอดมาเพื่อแก้ไขข้อบกพร่องบางอย่างของภาษา Java ในจุดประสงค์ที่แตกต่างกัน ...
Software Architecture Technology

3 Software Architecture Design ที่นิยมใช้พัฒนาระบบซอฟต์แวร์ขนาดใหญ่

ในการออกแบบซอฟต์แวร์ขนาดใหญ่ในองค์กร มักจะมีการทำงานร่วมกันโดยคนจำนวนมาก หากเราต่างคนต่างเขียนซอฟต์แวร์ไปในทางที่ตัวเองเห็นว่าดี ซอฟต์แวร์ที่แต่ละคนทำก็อาจจะทำงานร่วมกันไม่ได้หรือมีปัญหาตอนที่ Integrate เป็น Solution ใหญ่ ดังนั้น การทำซอฟต์แวร์ในระดับนั้นจึงจำเป็นต้องมีการแบ่งสันปันส่วน และมีการออกแบบ Software Architecture เพื่อให้ทำงานร่วมกันได้ดีและมองเห็นภาพรวมไปในทางเดียวกัน ทั้งระหว่างนักพัฒนาในทีมพัฒนากันเอง ...
Technology

สรุปเรื่อง Activity Lifecycle ของการพัฒนา Android app

Android Activity Lifecycle คืออะไร? ในการทำงานของแอปพลิเคชันจริง ๆ แล้วไม่ได้ทำงานเพียงแค่เฉพาะเวลาที่ผู้ใช้งานเปิดขึ้นมาบนหน้าจอเท่านั้น แต่ยังคงทำงานบางอย่างในขณะที่แอปพลิเคชันถูกย่อ หรือแม้กระทั่งตอนที่ผู้ใช้เปลี่ยนไปใช้แอปพลิเคชันอื่นอยู่ ซึ่งจริง ๆ แล้ว Android สามารถออกแบบกำหนดให้แอปพลิเคชันทำงานในสถานะต่าง ๆ ...
Technology

พร้อมแล้วหรือยัง!? ที่จะร่วมเดินทางเข้าสู่มิติคู่ขนาน ผ่านนิทรรศการออนไลน์สุดพิเศษ “Exclusive CO’XI” 

ปฏิเสธไม่ได้เลยกับคำว่า “Multiverse” หรือพหุจักรวาล ต้องเคยผ่านหูผ่านตากันมาบ้าง และอาจ ทำให้ใครหลาย ๆ คนจินตนาการถึงความแปลกใหม่ในอีกจักรวาลที่ขนานกับจักรวาลที่เราอยู่ในปัจจุบัน ซึ่งความแปลกใหม่นั้นอาจจะหมายถึงคน, สิ่งของ และรวมไปถึงนวัตกรรมใหม่ ๆ ที่เราไม่เคยเจอหรือไม่เคยมีมาก่อน เช่นเดียวกันกับ “Exclusive ...

Comments are closed.