ใน part one ของ series นี้ เราแนะนำ Devise โดยใช้ example app เพื่อสำรวจ modules, helpers, views, controllers และ routes
ใน part นี้ เราจะสำรวจ advanced usages ของ Devise มากขึ้น โดยเฉพาะการใช้ OmniAuth, API authentication และ Authtrail
มา dive straight in!
Authentication ด้วย OmniAuth สำหรับ Ruby
ในปัจจุบันเกือบทุก web application ที่คุณพบ จะเสนอ option ให้ login ผ่าน wide selection ของ authentication providers ตั้งแต่ social networks อย่าง Twitter และ Facebook ไป Google, GitHub และอื่นๆ อีกมากมาย
ในหลายกรณี convenient multi-provider authentication นี้ powered by library ที่เรียกว่า OmniAuth OmniAuth เป็น flexible และ powerful authentication library สำหรับ Ruby ที่อนุญาตให้คุณ integrate กับ multiple external providers
มันให้ simple และ unified API สำหรับเชื่อมต่อกับ various OAuth providers OmniAuth มีประโยชน์โดยเฉพาะใน situations ที่คุณอยากให้ users มี option sign up หรือ log in โดยใช้ social media accounts ด้วย OmniAuth คุณสามารถเพิ่ม social login functionality เข้าไปใน Rails application ได้ง่าย
เมื่อ OmniAuth ใช้ร่วมกับ Devise gem มันจะง่ายขึ้นอีกในการจัดการ user authentication และ authorization คุณสามารถ leverage Devise's built-in authentication features ขณะใช้ OmniAuth สำหรับ external provider sign-in options
Getting Started with OmniAuth และ Devise
เช่นที่กล่าวไว้ OmniAuth ให้คุณ integrate กับ number of third-party authentication providers สำหรับ purposes ของบทความนี้ เราจะใช้ GitHub
ติดตั้ง OmniAuth Gems
ใน Gemfile ของแอป เพิ่มบรรทัดต่อไปนี้:
และหากคุณใช้ OmniAuth 2.0+ gem คุณจะต้องเพิ่มอีก:
Gem omniauth-rails_csrf_protection ให้แน่ใจว่า GET requests ไป OAuth flow ถูก disabled มันยัง insert Rails CSRF token verifier ก่อน OAuth request phase สอง actions นี้ meant to mitigate cross-site forgery attacks ที่มุ่งไปที่ OAuth authentication flow
ต่อไป รัน bundle install เพื่อติดตั้ง gems
สร้าง GitHub OAuth App ใหม่
ตอนนี้เราต้องสร้าง OAuth app ใหม่บน GitHub App นี้จะทำหน้าที่เป็น user ที่มี authentication permissions ซึ่งสามารถ revoked ได้ง่าย หากจำเป็น
ขั้นแรกคือไปที่ settings page ภายใต้ GitHub account profile แล้ว left-hand menu คลิก "Developer settings" คุณจะเห็นหน้าจอแบบด้านล่าง ที่คุณสร้าง OAuth app ใหม่:

เมื่อคุณคลิก "Register new application" คุณจะได้หน้าจอแบบนี้:

กรอกรายละเอียดฟอร์มดังนี้:
- Application name - ให้ OAuth app ใหม่ชื่อที่เหมาะสม
- Homepage url - ตอนนี้ใช้
https://localhost:3000/ใน production คุณจะใช้ actual homepage url ของแอป - Application description - ไม่จำเป็น แตคุณยังสามารถมีได้หากคุณมีหลาย apps และต้อง differentiate ระหว่างพวกมัน
- Authorization callback url - นี่คือ required input และโดยทั่วไปจะ follow OAuth callback url format เช่น
https://<app-url>/users/auth/<application-provider>/callbackนั่น said บาง OAuth providers เช่น Google อาจไม่ follow format นี้ ดังนั้นคุณต้องจดบันทึกไว้
เมื่อทำเสร็จ คลิก "Register application" ในหน้าจอถัดไป generate app secret ใหม่ และจดมันไว้ที่ปลอดภัย (เพราะมันจะแสดงให้คุณเห็นแค่ครั้งเดียว)
Config Devise Initializer
เปิด Devise initializer config/initializers/devise.rb และ navigate ไป OmniAuth section specific to GitHub มันน่าจะ commented out ดังนั้น uncomment และ edit ด้วย ID และ secret ของ GitHub OAuth app ใหม่:
สร้าง OmniAuth Callbacks Controller
หากคุณ generate Devise controllers แล้ว คุณจะพบ OmniauthCallbacksController พร้อมสำหรับ customize ตามลำดับ หากไม่มี ให้สร้าง manual และ edit ดังนี้:
สองสิ่งที่ต้องสังเกตเกี่ยวกับโค้ดข้างต้น:
from_omniauthเป็น method ที่เราจะ implement ภายในUsermodelsign_in_and_redirectเป็น method ภายใน OAuth
เพิ่ม Migration เพื่อปรับ User Model
ตอนนี้เราต้องเพิ่มคอลัมน์บางส่วนไปที่ User model โดยเฉพาะคอลัมน์ provider และ uid:
รัน bundle exec rails db:migrate เพื่อ finalize ขั้นตอนนี้
ทำให้ Devise Model Omniauthable
ที่นี่ เราต้อง edit User model โดยเพิ่ม Devise Omniauthable module เข้าไป:
หลังจากนี้ เพิ่มเมธอด from_omni_auth นี้จะถูกเรียกจาก Users::OmniauthCallbacksController ที่เรา setup ไปเมื่อกี้:
ตอนนี้เราเหลือแค่หนึ่งอย่าง: เพิ่ม login links ไปที่ Devise views
Setup Login Links
โดย default Devise จะ automatically เพิ่ม login link ของ provider ที่เหมาะสมให้คุณใน user registration และ login views Link นี้จะใช้ GET method แตเรารู้ว่า OmniAuth 2.0+ ชอบ POST requests ดังนั้นเราต้อง disable link และ insert ของเราเองโดยใช้ POST requests:
ด้วยนั้น เรา successfully set up Ruby on Rails 7 app ด้วย Devise และ GitHub OAuth authentication คุณสามารถ grab full source code ของ companion app ได้ที่นี่
ต่อไป เราจะเข้า advanced use case อีกอย่าง: การใช้ Devise เพื่อ authenticate API call
API Authentication ด้วย Devise สำหรับ Ruby
วันนี้ ไม่ uncommon ที่ users คาดหวังจะ connect กับแอปของคุณ via API ใน section นี้ เราจะดูว่าเราสามารถ safely authenticate user requests เหล่านี้โดยใช้ Devise อย่างไร
ในขณะที่ browser-based authentication โดยทั่วไปเป็น cookie-based ส่วนใหญ่ API authentication เกิดขึ้นผ่าน tokens ที่เรียกว่า JSON Web Tokens (หรือ JWTs) ซึ่งถูก passed around ใน headers
Tip: สำหรับ purposes ของ section นี้ เราจะ assume เราทำงานกับ Rails API-only app เพื่อ follow along สร้างหนึ่งด้วย rails new app_name --api
JWT-based Authentication Flow
เช่นที่กล่าวไว้ API authentication based on JWT tokens และมันสำคัญที่เราเข้าใจว่า JWT-based authentication flow เกิดขึ้นอย่างไร Basically มัน follow general sequence of steps ตาม outline ด้านล่าง:
- User client ทำ call ไป API app
- API app responds ด้วย JSON Web Token (JWT) authentication token ที่ใช้แทน cookies
- Subsequent requests โดย user client ทำโดยใช้ token นี้ใน
Authorizationheader - User สามารถ hit Devise 'session destroy' action ซึ่งทำให้ token ถูก destroy และ user ถูก logged out
ตอนนี้ มา put flow นี้ into action เริ่มจากสิ่งที่เรียกว่า cross-origin resource sharing (CORS)
การตั้งค่า CORS
CORS ตั้งค่า API app ให้ allow requests จาก external sources CORS เป็น HTTP-based security policy ที่ define ว่า external requests จะถูก handled โดยแอปพลิเคชันอย่างไร โดย default CORS จะ block request ใดๆ ที่ originate จาก domain ต่างจากที่ทำ initial request (กล่าวคือ request มาจาก "origin" ที่ต่างกัน)
เพื่อ handle CORS properly เราจะใช้ nifty gem rack-cors ใน Gemfile uncomment บรรทัดด้านล่าง แล้วรัน bundle install:
เพิ่มเติม เปิด CORS initializer file ที่ตรงกันและปรับมันดังนี้:
important notes เกี่ยวกับสิ่งที่เราทำไป:
origins "*"- เพียงแค่หมายความว่า API app ของเราสามารถรับ requests จาก source ใดๆ ได้ตอนนี้expose: %w[Authorization Uid]- โดย defaultrack-corsgem ไม่ expose authorization และ UID headers แตเราต้องการพวกมันเพราะเราจะ pass authorization tokens ผ่าน
เมื่อทำเสร็จ มาติดตั้ง Devise และ Devise-JWT gem ที่มาพร้อมกัน
เพิ่ม Devise และ Devise-JWT Gems เข้า Rails App
Gem devise-jwt เป็น extension ของ Devise ที่จะให้เราทำงานกับ JWT tokens เพิ่ม gems ไป Gemfile แล้วรัน bundle install:
รัน Devise install generator bundle exec rails g devise:install
Generate และ Configure Models
เราต้อง setup สอง models: normal Devise user model (bundle exec rails g devise User) และ model ที่เราจะใช้สำหรับ revocation strategy (กล่าวคือ ว่าผู้ใช้จะ sign out ของ API ยังไง):
เรา modify normal Devise user model สำหรับ API authentication โดยเพิ่ม JWT token authenticatable module และ define token revocation strategy ใช้ model ที่สอง JwtDenylist:
ต่อไป เรา configure model ที่สองโดย reference revocation strategy และ revocation table ที่จะใช้:
ในส่วนต่อไป เราจะ outline token revocation และทำไมเราต้องการมัน
ความสำคัญของ Token Revocation
ทำไม token revocation สำคัญ? เพราะ JWT tokens เป็น stateless Server ไม่รู้อะไรเกี่ยวกับพวกมันนอกจากการ sign พวกมัน ใน scenario แบบนี้ server ไม่มีทาง sign out user โดย revoke corresponding token เนื่องจากไม่มีทาง revoke individual tokens เราต้อง build one out และบอก server ให้ใช้มัน
เมื่อ revoke token สิ่งที่เกิดขึ้นจริงภายใต้ hood คือ unique piece ของ token, jti (JWT ID) ถูก extract และใช้ตาม defined revocation strategy
แน่นอนสิ่งนี้ยกคำถามอีกอย่างว่า token revocation strategy คืออะไร In a nutshell มันคือ definition ของว่าการ revoke token จะถูก handled โดย server อย่างไร มีสาม basic revocation strategies:
- JTIMatcher strategy - ที่นี่ unique column ชื่อ "jti" ถูกเพิ่มไปที่ user model ซึ่งก็ทำหน้าที่เป็น revocation table ทุกครั้งที่ user ทำ request
jtiใน header จะ matched against stored tokens และ access allowed เฉพาะเมื่อ match พบ - Denylist strategy - สำหรับอันนี้
jtiและ token expiry (expของ revoked tokens) ถูกเก็บใน database table สำหรับทุก request ที่ user ทำ check จะทำ against table นี้ เปรียบเทียบjtiของ current token ของ user ต่อ revoked ones ใน database หาก match พบ request ของ user นั้นถูก denied - Allowlist strategy - ในทางหนึ่ง strategy นี้คล้าย strategy แรก แตที่นี่ table ที่เก็บ JWT IDs อยู่ใน one-to-many relationship กับอีก table ที่เก็บ user tokens ทุกครั้งที่ request ทำ
jtiของ user ที่เก็บใน Allowlist table matched against สิ่งที่เก็บใน matching table พร้อม tokens Access allowed เฉพาะเมื่อ match พบ
แน่นอนนี่เป็น overview ที่ simplified มากของ token revocation และคุณสามารถเรียนรู้เพิ่มเติมได้ที่นี่
Set Up Signing Key สำหรับ JWT Tokens
เนื่องจากเราจะใช้ secure tokens authenticate users และ requests ของพวกเขา เราต้องการวิธี sign พวกมัน นี่คือที่ที่ secret key เข้ามา แนะนำให้ generate new key ต่างจาก Rails secret key, secret_key_base
รัน bundle exec rake secret เพื่อ generate unique key แล้ว edit Devise initializer ให้ include key นี้:
สุดท้าย มา setup controllers
Controller Set Up
ขั้นตอนสุดท้ายในการ implement Devise สำหรับ API authentication คือ setup controllers ของเรา เพื่อ simplicity เราจะ setup สอง controllers: หนึ่งสำหรับ registration และอีกหนึ่งสำหรับ sessions
มา create เอง เริ่มจาก registrations controller:
สิ่งที่เกิดขึ้นกับ controller นี้:
- เรา config ให้ respond to requests ด้วย JSON
- เราระบุ
respond_withaction ที่คืนค่า result ของ successful หรือ failed registration
และตอนนี้ sessions controller:
เหมือน registrations controller ที่นี่เราระบุว่า controller จะ respond ด้วย JSON เรายัง define respond_with action สำหรับเมื่อ user login สำเร็จ และ respond_to_on_destroy เพื่อ handle user signing out
และด้วยนั้น คุณควรมี working API authentication flow powered by Devise และ JWT tokens!
ส่วนสุดท้ายของเราจะ quick look ที่ว่าคุณสามารถ track user logins โดยใช้ Devise และ Authtrail อย่างไร
การ Track Devise Logins ด้วย Authtrail
สมมติคุณอยากส่ง notification email ไปให้ app users ทุกครั้งที่มีคน login เข้าบัญชี พร้อม details เช่น IP address และ timestamp ของ login คุณจะ accomplish สิ่งนี้อย่างไร?
คุณจะต้อง track user logins แล้วใช้ข้อมูลนั้นใน notification emails ที่คุณส่งให้ users แตก่อน คุณจะต้องการวิธี track user logins สิ่งนี้สามารถ accomplish ได้โดยใช้ nifty gem ที่เรียกว่า Authtrail ซึ่งก็ pair well กับ Devise
การติดตั้ง Authtrail
ขั้นแรกคือติดตั้ง gem ด้วย bundle add authtrail เพิ่มเติม เนื่องจากคุณจะ store user-identifiable information เช่น emails และ IP addresses ใน app database มัน highly recommended ที่คุณ encrypt data นี้ใน production โดยใช้ combination ของ Lockbox และ Blindindex gems
ต่อไป รัน Authtrail generator เพื่อสร้าง initializer และ accompanying table migration ที่จะเก็บ login data:
Authtrail ทำงานอย่างไร
ทุกครั้งที่ user พยายาม log in Authtrail record ใหม่ถูกสร้างพร้อม details สำคัญต่อไปนี้:
- Login email address ที่ใช้
- Whether login successful หรือไม่
- Reason ที่ login failed (ถ้า login เป็น failure)
- User's IP address,
referrerและอีกมากมาย
คุณสามารถใช้ข้อมูลนี้ได้ตามที่คุณต้องการ ตัวอย่างเช่น คุณสามารถส่ง notification email ให้ user รวมถึง email และ IP address information เพื่อแจ้งให้พวกเขารู้ว่ามี login attempt บนบัญชีของพวกเขา
อ่าน Authtrail's documentation เพื่อดู possibilities ทั้งหมดที่ available ให้คุณ
สรุป
ใน series นี้ เรา deep dive เข้า Devise gem
ก่อนอื่น เราจับ basics ของ Devise รวมถึงว่า modules, helpers, views, controllers และ routes ทำงานอย่างไร ใน part สองและ final นี้ เราสำรวจการใช้ Devise กับ OAuth, Authtrail และสำหรับ API authentication
หวังว่า series นี้จะเป็น helpful guide สำหรับ all things Devise authentication
Happy coding!
P.S. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่เผยแพร่ ให้สมัครสมาชิก newsletter Ruby Magic ของเรา และจะไม่พลาดโพสต์เด็ดๆ ไป!