Coding Gun

ใช้งาน JWT(JSON Web Tokens) ยังไงให้ปลอดภัย

JWT(Json Web Tokens) คือ มาตรฐานที่บอกวิธีการใช้ Token สำหรับการ Authentication ให้ปลอดภัย โดยจะใช้ JSON ในการรับส่งข้อมูล ซึ่งเราสามารถตรวจสอบว่าข้อมุลที่ฝังอยู่ใน token นั้นถูกแก้ไขหรือไม่(Integrity) กระบวนการทำงานของ JWT จะมีขั้นตอนต่างๆ ตามรูปนี้

JWT workflow

ขั้นตอนการทำงานของ JWT

  1. ถ้า User จะต้องส่ง Username และ Password เข้าไป Authentication ก่อน
  2. ถ้า Username กับ Password ที่ส่งมาถูกต้อง Server จะทำการ Sign Token(สร้าง Token) ด้วย Secret ดูวิธีการ Sign Token ด้านล่าง
  3. ส่ง Token กลับไปยัง browser
  4. หลังจากนี้ถ้าเราต้องการใช้งาน service อะไรเราก็จะส่ง JWT ไปทาง Header
    Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd... 
    
  5. เมื่อ Server ได้รับ JWT Server ก็จะทำการตรวจสอบความถูกต้องของ JWT(Validate Token)
  6. ส่งผลลัพธ์กลับไปยัง client

Server ที่ทำหน้าที่ Sign และ Validate Token ไม่จำเป็นต้องเป็นตัวเดียวกัน

JWT vs API Key

เราจะใช้ JWT และ API Key ในการทำ Authentication เหมือนกันแต่ความแตกต่างของ JWT และ API Key คือ JWT จะมี expire(หลังจาก expire แล้ว token จะไม่ valid แล้ว) ส่วน API Key คุณต้องยกเลิกเองเมื่อเวลาผ่านไป

JWT หรือ API Key ถ้ายิ่งอยู่นานยิ่งมีความเสี่ยง

นอกจากนี้ JWT จะเก็บข้อมูลไว้ใน Token(By Value) ในขณะที่ API Key ไม่สามารถเก็บข้อมูลได้(By Reference) นั่นคือถ้าต้องการรู้ว่า API Key นี้ยังใช้งานได้อยู่หรือไม่ต้องไปถามคนที่ Generate API Key ขึ้นมา ในขณะที่ JWT เราสามารถแกะดูข้่อมูลใน body และ validate ได้แม้จะไม่ใช่คน Sign Token ก็ตาม

JWT vs Session

การใช้งาน JWT ต่างจากการใช้งาน Session ยังไง? นี่น่าจะเป็นคำถามที่หลายคนสงสัย สิ่งที่ทำให้ JWT แตกต่างจาก Session คือ

แล้ว Stateless กับ Stateful แตกต่างกันยังไง ลองนึกถึงร้านอาหารและกาแฟ เวลาเราเก็บสะสมแต้มจะมีอยู่ 2 รูปแบบ คือ

  1. ร้านเป็นคนเก็บข้อมูล เราแค่บอกเบอร์โทรศัพท์ของเราจะได้คะแนนจากการซื้อของครั้งนั้นทันที นั่นคือร้านค้า(Server) เป็นคนเก็บข้อมูลให้เรา แบบนี้เรียกว่า Stateful หมายถึง Server ทำการเก็บข้อมูลให้เรา(Session ก็จะเก็บข้อมูลไว้บน server)

  2. ร้านไม่เก็บข้อมูลแต่ใช้การปั๊มตราลงในบัตรสะสมแต้มแล้วให้ลูกค้าเก็บไว้(Client) ทุกครั้งที่คุณจะสะสมแต้มหรือแลกของคุณต้องเอาบัตรสะสมแต้มมาด้วย นั่นคือ Stateless หมายถึง client เป็นคนเก็บข้อมูล(JWT จะเก็บอยุ่ที่ฝั่ง client ส่วนใหญ่อยู่ใน local storage)

แล้วทีนี้ JWT(Stateless) และ Session(Stateful) มีข้อดีและข้อเสียยังไง

ข้อดีของ Session(Stateful) คือ

  1. การจัดเก็บ Session อยู่ฝั่ง Server ดังนั้นจะปลอดภัยว่า
  2. เขียน Code สั้นกว่า ไม่ต้องทำอะไรเลย เพราะวิธีการทำนั้นตรงไปตรงมา ไม่มีทางเลือกอะไร
  3. การ revoke(ห้ามเข้าใช้งาน) ทำได้ง่าย
  4. Session ID ถูกเก็บอยู่ใน Cookie ซึ่งมี HttpOnly และ Secure Flag ในการป้องกัน Cross-Site Scripting(XSS)
  5. กำหนดช่วงเวลา Lifetime สั้นกว่า Session จะไม่ expire ตราบใดที่ user ยังใช้งานอยู่

ข้อเสียของ Session(Stateful)

  1. ยิ่งมีข้อมูลอยู่บน Session เยอะ ยิ่งทำให้ Server ทำงานหนัก
  2. Scale ได้ยาก เพราะทุกอย่างอยู่บน Server

ข้อดีของ JWT(Stateless)

  1. สามารถขยายตัวได้ง่าย(Scalability)

ข้อเสียของ JWT(Stateless)

  1. ต้องกำหนดให้ Token มีอายุการใช้งานยาวเพียงพอต่อการใช้งาน(ยิ่งอยู่นานยิ่งมีความเสี่ยง)
  2. ไม่มีมาตรฐานในการ revoke token(ยกเลิกการใช้งาน)
  3. ความปลอดภัยของ JWT ขึ้นอยู่กับ algorithm, ความยาวของ secret และ awareness ของ developer

ส่วนประกอบของ JWT

JWT นั้นแบ่งเนื้อหาออกเป็น 3 ส่วนด้วยกันตือ

หลังจาก encode ข้อมูลทั้ง 3 ส่วนแล้วเราก็จำนำมารวมกันโดยเชื่อมทั้ง 3 เข้าหากันด้วยเครื่องหมาย .

Bas64Encode(Header).Bas64Encode(Payload).Bas64Encode(Signature)

JWT มีวิธีการตรวจสอบ(Validate)ยังไง?

Header ของ JWT เป็นส่วนที่บอกว่าเลือก algorithm ไหนมาใช้ Sign Token ซึ่งการ Sign จะทำได้ 2 วิธีคือ

  1. Hashing เราจะใช้ Secret ตัวเดียว นั่นทำให้ server ที่ Sign Token และ Validate Token ต้องเป็นตัวเดียวกัน algorithm ที่เลือกใช้จะเป็น

    • HS256
    • HS384
    • HS512

    ทั้ง 3 ตัวนี้เป็น algorithm HMAC เหมือนกันแต่ความยาวของ Secret ต่างกัน(ตัวเลขข้างหลังหมายถึงความยาวของ secret หน่วยเป็น bit)

  2. Encryption + Hashing เราจะเข้ารหัส Signature ด้วย Private Key(Sign Token) ส่วน Public Key จะใช้ตรวจสอบ(Validate) Token พอ Key ที่ใช้ในการ Sign กับ Validate เป็นคนละตัว เลยทำให้เราสามารถเลือกที่จะให้ Server ที่ทำหน้าที่ Sign และ Server ที่ทำหน้าที่ Validate เป็นคนละตัวได้

    Algorithm ที่สามารถเลือกใช้ได้มี 3 กลุ่มด้วยกันคือ

    • RS256, RS384, RS512 กลุ่มนี้จะ Encryption ด้วย RSA และ Hash ด้วย SHA256, SHA384 หรือ SHA512 ตามตัวเลขที่ต่อท้ายด้านหลัง เช่น RS512 จะมีขั้นตอนการเข้ารหัส ดังนี้
      1. ใช้ Private key encrypt payload ด้วย algorithm RSA
      2. หลังจากนั้นจะนำ cipher text(ผลลัพธ์ที่ได้จากการเข้ารหัส) ไป hash ด้วย SHA512
    • ES256, ES384, ES512 กลุ่มนี้จะ
      1. Encryption ด้วย ECDSA(ความปลอดภัยสูงกว่า RSA)
      2. Hash ด้วย SHA256, SHA384 หรือ SHA512 ตามตัวเลขที่ต่อท้ายด้านหลัง
    • PS256, PS384, PS512 กลุ่มนี้จะ
      1. Encryption ด้วย RSAPSS(เป็น RSA ที่กำหนด Version ที่จะใช้ในการ Sign)
      2. Hash ด้วย SHA256, SHA384 หรือ SHA512 ตามตัวเลขที่ต่อท้ายด้านหลัง

    ข้อควรระวังคือห้ามใส่ “alg”: none เพราะจะหมายถึงเราไม่มีการใช้ Signature นั่นก็คือใครอยากแก้อะไรใน Payload ก็ทำได้เลยตามสบาย

ทำไมเราไม่ควรใช้ HS256, HS384 และ HS512

เราควรเลือกใช้ RS512 แทน HS256, HS384 และ HS512 เพราะเราสามารถ Brute force หา Secret ที่เราใช้ในการทำ Hashing ด้วย jwt_cracker ซึ่งขั้นตอนของการค้นหา secret จะมีดังนี้

  1. Install JWT Cracker

    $ npm install jwt-cracker
    
  2. Crack JWT Secret

    $ jwt-cracker -t [token]
    

    จำนวนอักขระยิ่งยาว ยิ่งใช้เวลานานในการ brute force ดังนั้นถ้าคุณยังต้องใช้ HS อยู่ แนะนำให้ใช้ HS512 และตั้ง Secret อย่างน้อย 64 ตัวอักษร และ Secret ต้องมาจากกการ random เท่านั้นอย่าเอาชื่อ application หรือ project เข้ามาใส่โดยเด็ดขาด

    ตัวอย่าง เราจะ Brute force JWT ด้วย jwt-cracker

    $ jwt-cracker -t "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.cAOIAifu3fykvhkHpbuhbvtH807-Z2rI1FS3vX1XMjE"
    

    ผลลัพธ์ที่ได้ออกมาตือ JWT Token นี้ Sign ด้วย algorithm HS256 และใช้ secret เป็น Sn1f

    Attempts: 100000
    Attempts: 200000
    Attempts: 1600000
    SECRET FOUND: Sn1f
    Time taken (sec): 21.733
    Attempts: 1640000
    

อ่านต่อเพิ่มเติมได้ที่

Phanupong Permpimol
Follow me