Web Authentication API feature ใหม่ที่ทำจะให้คุณ login website ได้ด้วยลายนิ้วมือ และ FIDO

กว่าจะเขียนบล็อกนี้ได้ นั่งงัด code อยู่หลายวันเลย

เล่าเรื่อง Web Authentication API ก่อน…

พูดถึงปัญหาก่อน ในสมัยก่อนตัวเว็บจะไม่สามารถเข้าถึงพวก Security key หรือ Authentication ของเครื่องคอม อย่างพวก ลายนิ้วมือ หรือ user login ของคอม อย่างง่ายๆได้เลยทำให้เราจำเป็นที่ต้องใช้ของอย่าง Username password ซึ่งหายทีก็เละเลย คนที่มีปัญหานี้สุดก็คือ FIDO ซึ่งเป็นองค์กรที่พัฒนาอุปกรณ์ ้ 2 Factor Authentication ที่จะใช้งานได้ก็ต้องให้ Browser support ก่อน คราวนี้ ไอ้อุปกรณ์แบบนี้มันไม่ได้มีแต่ FIDO อย่างเดียว พวก Finger scan บนเครื่อง pc หรือมือถือ ก็ควรที่จะใช้ได้เหมือนกันเพราะ FIDO มันไม่ได้หากันง่ายๆ แกงค์คนทำ browser กับ Yubico (คนผลิต FIDO เจ้าดัง) ก็เลย ร่าง Spec API ที่ชื่อว่า Web Authentication API ขึ้นมา เพื่อให้เว็บสามารถเข้าถึงอุปกรณ์เหล่านี้ได้

Docs ที่มีอยู่หลายๆที่มันใช้ไม่ได้

จริงๆแล้ว MDN หรือ Google เนี่ย ได้ปล่อย Document ของ เจ้านี่ออกมานานมากๆแล้วแต่ว่า เอาเข้าจริง มันทำตามนั้นตรงๆไม่ได้….เพราะมันไม่ได้พูดฝั่ง server เลย ก็เลยไปนั่งดู Demo ของ Google ที่ https://webauthndemo.appspot.com/ ว่ามันทำงานยังไง แต่พอจะเข้าไปดู code มันก็เป็น JAVA ที่ผมเองก็ไม่ได้เข้าใจมันมากเพราะตัวเองก็ไม่ได้เขียน JAVA พอไปลองหาเวอร์ชั่น Javascript ก็ไปเจอของ FIDO เองตัวนี้ https://github.com/fido-alliance/webauthn-demo แต่มันก็ไม่ support touch ID ของเครื่อง mac รวมถึง API หลายๆอย่างก็ไม่ได้ Support จนกระทั่งต้องมานั่งไล่ code เองไปเรื่อยๆ เพื่อทำ Demo ของตัวเอง ซึ่งก็ไปเอา ของ FIDO เนี่ยแหละมาปรับใหม่ ถ้าใครอยากจะลองไปอ่าน code ก็ไปอ่านได้ที่ https://github.com/thangman22/webauthn-demo ในนั้น ผม เขียนคำอธิบายทีละ step ไว้ให้แล้วนะครับ ( ลอง find หา คำว่า “STEP” หรือ ไล่ดู TODO ดู)

วิธีการ implement

อย่างแรกต้องเข้าใจก่อนว่า Imformation หลายๆอย่างที่ อยู่ใน feature นี้เป็น Type Buffer ซึ่งถ้าจะส่งข้ามไปกลับจาก Server ต้อง convert เป็น Base64 ก่อนเสมอ โดยวิธี implement ที่จะเล่า จะเอาที่ต้องทำจริงๆมาเล่าเลย ไม่เอาแค่ sample ซึ่ง Methods ที่จะใช้ในหน้าบ้าน หลักๆจริงๆมีแค่ navigator.credentials.create และ navigator.credentials.get ที่เหลืออยู่หลังบ้านล้วนๆ

เริ่มที่ Register

  1. ในตัวอย่างก็มักจะบอกเราว่า เรียก navigator.credentials.create แล้วส่งค่า option หน้าตาแบบข้างล่างได้เลย แต่ปัญหาก็คือ…….
    มันควรสร้างมาจาก server ไม่ใช่ client ซึ่งมีจุดสำคัญอยู่หลายจุดเหมือนกันที่เราจะต้องเข้าใจก่อนสร้างออกมา
  • User จะเห็นว่าตัว method ต้องการ user รวมถึง ID ของ user นั้นหมายความว่า ก่อนจะ register device เราจำเป็นที่จะต้อง ให้ user login ก่อน แล้วค่อยทำการผูก 2 step ลงไป
  • Attestation คือ เราจำเป็นต้องใส่ direct ไว้เพราะว่า ถ้าสมมติ เราไม่ใส่ เวลาที่จะ verify ใน server ก็จะไม่รู้ว่าเป็น type ไหน
  • challenge อันนี้ concept คือ random อะไรก็ได้ออกมาจาก server เพื่อนตอนที่ส่งกลับไป จะเอาไปเช็คได้ว่า มันถูกสร้าง จาก server จริงๆไม่ใช่โดน fake มา ซึ่งอย่าลืมว่า ใน server save ลง cookie ไว้ด้วย
  • จะเห็นว่ามีบางอย่างเป็น buffer นั้นตอนส่งมาเป็น string จาก server แปลงเป็น buffer ก่อนนะ
  • จริงๆแล้วเนี่ย ถ้าจะให้ใช้ Touch ID ได้ เราต้องกำหนด property authenticatorSelection.authenticatorAttachment เป็น “platform” ด้วย ถ้าไม่กำหนด default จะเป็น “cross-platform” ซึ่งจะไปเรียกใช้ FIDO แทน แล้ว object สุดท้ายหน้าตาจะประมาณนี้
  • หมายเหตุ: สามารถเช็คได้ว่า support TouchID มั้ย ผ่าน method นี้ PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
{
authenticatorSelection: {
authenticatorAttachment: “platform”,
requireResidentKey: false,
userVerification: “required”
}
}

2. พอเราเรียก methods create เสร็จ เราก็จะได้ dialog หน้าตาแบบนี้ขึ้นมา

แล้วเราก็จะได้ object นึงจาก promise ของ methods ซึ่งง่ายๆครับ โยนมันไปที่ server ทั้งก้อนเลย

3. คราวนี้มันจะยุ่งยากและ เพราะส่งมาที่ server มันไม่ได้จบแค่นั้น หลังจากที่เราได้ information ทั้งหมดและส่งกลับมาที่ server แล้ว สิ่งที่เราต้องทำต่อคือ verify ว่าที่สิ่งส่งกลับมามันถูกมั้ยโดยต้องทำตามนี้

  • เช็คก่อนว่า challege ตรงกับที่ส่งไปจากขั้นตอนข้างบนมั้ย โดยที่ปกติแล้วเราก็เช็คกับ cookie ที่เราเก็ย challege ไว้
  • ความยากมันอยู่หลังจากนี้เป็นต้นไป เพราะว่า เราไม่รู้เลยว่าสิ่งที่เราได้มานั้นถูกปลอมมาหรือเปล่าเป็น man made หรือเปล่า นั้นเราจำเป็นที่จะต้อง extract data ออกมาก่อน เพื่อเช็คความถูกต้อง ซึ่งกระบวนการนี้เรียกว่าการ verifyAttestation แล้ว แต่ละอุปกรณ์ก็จะมีวิธีการ verify ที่ต่างกันออกไปอีกแบ่งออกไปเป็น Type ใหญ่ๆได้สาม type คือ fido, packed (ลายนิ้วมือใช้อันนี้),android-safetynet ซึ่งผมได้เอา method หาร verify จาก คุณคนนี้ https://medium.com/@herrjemand มาใส่ใน demo แล้ว
  • เมื่อเราทำการ verify ว่าส่ิงๆนี้เป็นของจริงๆแล้วสิ่งที่เราต้องทำต่อก็คือ เราต้องเก็บ rawId เอาไว้ พร้อมกับ publickey เพื่อใช้ในการเช็คตอนที่ login อีกครั้ง
  • จริงๆแล้วเราสามารถเก็บ clientDataJSON เพื่อเอาไว้เช็คก็ได้นะ แต่ไม่แนะนำเลยเพราะว่า เราไม่รู้เลยว่า ข้อมูลทั้งหมดจะถูกปลอมมามั้ย

ต่อกันด้วย Login

  1. ถ้าจำกันได้ เราได้ทำการ save ไว้แล้วว่า User ไหน มี rawId อะไร เพราะฉะนั้นสิ่งที่ต้องทำอย่างแรกคือ ให้ user Login จากหน้าบ้านก่อน หรือ จะแค่กรอก user name ก็ได้หลังจากนั้นเราก็ไป Query ใน Database ดูว่า User นั้น มี Credential ID อะไร หลังจากนั้นเราก็สร้าง object หน้าตาประมาณนี้ส่งออกมาที่หน้า front end เพื่อใช้ใน method navigator.credentials.get

3. พอเราเรียก method navigator.credentials.get Dialog แบบเดิมก็จะโผล่ขึ้นมาอีกครั้ง (จริงๆตรงนี้อยู่ที่เราบอกด้วยว่า support device อะไรบ้าง) เพื่อให้ user scan ลายนิ้วมือ หลังจากนั้นก็เหมือนเดิมครับโยน response ทั้งหมด ไปที่ server

4.พอส่งข้อมูลกลับไปที่ server เราก็ต้องทำอย่างเดิมอีกครั้งครับคือ เทสก่อนว่า Challenge เนี่ยตรงกับ ที่ส่งมาจาก server มั้ย

5. หลังจากนั้น ให้เอา public key มาลอง verify ดูว่า signature นั้นตรงกับที่เก็บใน Database ของเรามั้ย

6. ถ้าตรงก็ถือว่า login สำเร็จ

เอาไปทำอะไรได้บ้าง

ก็คงสงสัยว่าเจ้านี่เนี่ยเอาไปทำอะไรได้บ้าง ก็จริงๆแล้วทำได้อยู่อย่างสองอย่าง

  • 2 factor authentication — อันนี้เราเห็นกันบ่อยอยู่แล้ว ที่พอกรอก password เสร็จ ก็จะให้เสียบ USB หรือ ไป verify จากอีก device นึงก่อน
  • Passwordless login — อันนี้จะคล้ายๆใน demo คือ พอเรา ใส่ แค่ username ก็ให้ authen ผ่าน device ได้เลย
  • Auto login — เคสนี้คือ ให้ user เสียบอุปกรณ์เอาไว้ ถ้าสมมติว่ามี อุปกรณ์เสียบอยู่ก็ให้ login ได้เลย

Browser อะไร Support บ้าง?

ตอนนี้เอาจริงๆเหลือแค่ Safari version stable ที่ยังไม่ support feature นี้ แต่ในตัว Technology preview เริ่ม support แล้ว เพราะฉะนั้น ก็เป็น สัญญาณอันดีว่ามันจะ support เร็วๆนี้

ลองไปอ่านดู code

จริงๆอธิบายแค่นี้ ก็ดูเหมือนจะไม่มีอะไรยากแต่จริงๆแล้วขั้นตอนข้างหลังเยอะมากๆ เพราะฉะนั้น อย่างที่ผมบอกไปตอนต้น ให้ลองเข้าไปใน repo นี้ https://github.com/thangman22/webauthn-demo และ find หาคำว่า STEP หรือ TODO ใน project ในนั้น ผมเขียนอธิบายแต่ละขั้นตอนไว้ให้แล้ว เพื่อได้เข้าใจอย่างละเอียด แต่ถ้าใครไม่อยากต้องทำความเข้าใจเยอะก็เอา code ไปใช้ได้เลยครับ