- vừa được xem lúc

Identity Provider with OAuth 2.0 and OpenID Connect

0 0 6

Người đăng: Nguyễn Nam Thắng

Theo Viblo Asia

Ở bài trước Setting Up Authentication with Devise, chúng ta cài đặt Devise để thực hiện một số chức năng liên quan đến Authentication.

Trong bài này chúng ta cũng sẽ tìm hiểu về doorkeeperdoorkeeper-openid_connect gems để cài đặt server với các chức năng của OAuth2.0 và OpenID Connect.

Trước tiên mình cần thêm các thuộc tính khác vào trong bảng User để phục vụ cho mục đích sign_incall APIs sau này của các Client.

  • NamePhone number: tương ứng với scope: profile
  • UUID: sẽ sử dụng để làm sub để trả về cho Client.
    • Lý do mình sử dụng UUID ở đây mà không dùng ID của của User là vì id là một integer tự động tăng và nó khá nhảy cảm nếu để bị lộ ra bên ngoài, attacker hoặc đối thủ có thể biết được số lượng users hiện tại của chúng ta và thực hiện các hành vi tiêu cực.
    • Đối với uuid nó là một dãy ký tự với độ phức tạp cao, không thể trùng lặp và không mang ý nghĩa vì vậy rất phù hợp để làm sub trong OAuth2.0 và OpenID Connect.

OK, xem chi tiết các thay đổi trong Pull Request này nhé mọi người.

Note: à phần before_create :set_uuid, đang bị sai vì trước khi tạo record nó sẽ thực hiện validation và thấy rằng uuid không tồn tại nên sẽ không thực hiện được. Vì vậy chúng ta cần phải thêm uuid trước khi validation và chỉ đối với hành động create thôi, nên code đúng sẽ là: before_validation :set_uuid, on: :create

Basic Configuration and Installing Doorkeeper

Link pull request: https://github.com/learnforward2023/stid_web/pull/4

Doorkeeper là gem (Rails engine) giúp bạn dễ dàng giới thiệu chức năng của ID Provider OAuth2.0 & OIDC cho ứng dụng Ruby on Rails.

gem 'doorkeeper'
gem 'doorkeeper-openid_connect'

Chạy lệnh bundle install

➜ bundle install Bundle complete! 16 Gemfile dependencies, 106 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

Basic configuration

Doorkeeper cung cấp cho chúng ta một số commands để tạo các config cơ bản:

  • rails g doorkeeper:install : khởi tạo các config cơ bản cho các chức năng của OAuth2.0.
  • rails g doorkeeper:openid_connect:install : khởi tạo các config cơ bản cho các chức năng của OpenID Connect.
  • rails db:migrate : chạy DB migration cho ứng dụng.

Chạy tất cả các lệnh bên trên và bạn sẽ thấy chúng ta có hai file config mới trong phần config/initializers

  • doorkeeper.rb
  • doorkeeper_openid_connect.rb

Doorkeeper

Đầu tiên hãy chú ý vào phần config cho OAuth2.0 trong file doorkeeper.rb trước.

# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do raise "Please configure doorkeeper resource_owner_authenticator block located in #{__FILE__}" # Put your resource owner authentication logic here. # Example implementation: # User.find_by(id: session[:user_id]) || redirect_to(new_user_session_url)
end

Phần này giúp chúng ta kiểm tra xem liệu resource_owner đã authenticated (đăng nhập) ở phía ID Provider hay chưa. Bạn có thể thêm các logic để kiểm tra xem user đã đăng nhập chưa trong block này.

  • Thông thường nếu user đã đăng nhập thì session sẽ lưu thông tin của người dùng vì vậy việc kiểm tra session ở đây là điều có thể làm. Hoặc bạn có thể sử dụng các helper methods của Devise ở đây cũng được.

Tiếp theo là admin_authenticator

# If you didn't skip applications controller from Doorkeeper routes in your application routes.rb
# file then you need to declare this block in order to restrict access to the web interface for
# adding oauth authorized applications. In other case it will return 403 Forbidden response
# every time somebody will try to access the admin web interface.
#
# admin_authenticator do
# # Put your admin authentication logic here.
# # Example implementation:
#
# if current_user
# head :forbidden unless current_user.admin?
# else
# redirect_to sign_in_url
# end
# end

Phần này giúp chúng ta kiểm tra xem liệu người dùng hiện tại có phải là Admin hay không? Vì chỉ có Admin mới có quyền truy cập vào các trang quản lý oauth_applications để đăng ký Client.

  • Sau này chúng ta sẽ thêm một trường mới trong bảng User là :admin để thực hiện chức năng này.

Giờ đến với scopes

# Define access token scopes for your provider
# For more information go to
# https://doorkeeper.gitbook.io/guides/ruby-on-rails/scopes
#
# default_scopes :public
# optional_scopes :write, :update

Chúng ta có hai options là:

  • default_scopes : là những scopes mặc định cho các yêu cầu uỷ quyền. Nói cách khác, nếu Client không truyền tham số cho scopes trong authorization_request thì đây là những scopes mà chúng sẽ được chỉ định.
  • optional_scopes : là những scopes mà ID Provider chấp nhận uỷ quyền. Nói cách khác, đây là danh sách các scopes mà Client có thể lựa chọn khi đăng ký client với ID Provider.

Cuối cùng là skip_authorization.

# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
# For example if dealing with a trusted application.
#
# skip_authorization do |resource_owner, client|
# client.superapp? or resource_owner.admin?
# end

Đây là phần giúp bạn có thể đưa ra quyết định xem liệu có hiển thị màn hình authorization cho user hay không? Chúng ta có hai loại Client là confidentialnon-confidential .

  • Đối với non-confidential thì chúng ta phải luôn hiển thị màn hình authorization cho user và nhận consent từ người dùng.
  • Mặt khác đối với các ứng dụng confidential thì họ có thể quyết định xem có nên hiển thị màn hình authorization cho user hay không, dựa trên chính sách của Client.

Note: màn hình authorization là màn hình khi người dùng xác thực thành công, ID Provider sẽ hiển thị một màn hình nhắc người dùng rằng: "Hêy! Client XXX muốn thay mặt sử dụng tài khoản của bạn, họ sẽ có quyền truy cập vào .... (scopes mà client đăng ký). Bạn có ổn với điều đó không?".

  • Chắc bạn sẽ quen thuộc hơn với trường hợp đăng nhập bằng Facebook và Google vào các ứng dụng khác, bạn sẽ nhận được câu hỏi đồng ý để ứng dụng sử dụng thông tin bạn.

Còn rất nhiều thông tin khác trong file doorkeeper.rb nhưng chúng ta sẽ tìm hiểu thêm sau nếu có cơ hội.

Doorkeeper OpenID Connect

Giờ thì chuyển sự chú ý của chúng ta sang file doorkeeper_openid_connect.rb để xem chúng ta có những config nào cho OpenID Connect nào!

Đầu tiên là signing_key, đây sẽ là một private_key để chúng ta ký vào id_token và trả về cho Client. Khi nhận được id_token Client cần lấy các thông tin từ ID Provider để verify.

signing_key <<~KEY -----BEGIN RSA PRIVATE KEY----- .... -----END RSA PRIVATE KEY-----
KEY

Bên dưới một chút là subject_types_supported, xác định cách mà nhà cung cấp OpenID Connect biểu diễn danh tính của người dùng trong các ID Token.

subject_types_supported [:public]
  • public : Giá trị sub (subject) trong ID Token là duy nhất cho tất cả các Client. Điều này có nghĩa là cùng một sub sẽ được sử dụng cho cùng một người dùng trên tất cả các Client. Đây là lý do vì sao mình quyết định sử dụng uuid để làm sub.
  • pairwise : Giá trị sub trong ID Token là duy nhất cho mỗi ứng dụng khách. Điều này có nghĩa là cùng một người dùng sẽ có các giá trị sub khác nhau cho các ứng dụng khác nhau. Điều này giúp bảo vệ sự riêng tư của người dùng.
  • ephemeral : Đây là một loại ít phổ biến hơn, chỉ sử dụng giá trị sub duy nhất trong phiên hiện tại và không được sử dụng lại.

Trong dự án lần này chúng ta sẽ chỉ hỗ trợ subject_type là public. Giờ thì hãy đi qua nhanh một số block khác.

  • resource_owner_from_access_token : được sử dụng để tìm và trả về thông tin của người sở hữu tài nguyên (resource owner) dựa trên access token.
  • auth_time_from_resource_owner : trả về thời gian xác thực của resource owner bên trong ID Token nếu Client thêm tham số max_age trong authorization request.
  • reauthenticate_resource_owner : phương thức này được gọi khi cần xác thực lại resource owner.

Cuối cùng hãy đến với subject.

subject do |resource_owner, application| # Example implementation: # resource_owner.id # or if you need pairwise subject identifier, implement like below: # Digest::SHA256.hexdigest("#{resource_owner.id}#{URI.parse(application.redirect_uri).host}#{'your_secret_salt'}")
end

Đây sẽ là phần triển khai để quyết định xem giá trị sub trong ID Token trả về cho Client là gì? Như đã đề cập trong các phần trước đó, chúng ta sẽ sử dụng uuid để làm sub. Nhưng hãy cập nhật nó sau vậy, giờ hãy để yên như thế.

Còn một số phần khác để customize claims cho ID Token bạn có thể đọc thêm trong file.

Make an API for Test in Future

Đầu tiên mình muốn sửa lại một số configuration và UI một chút.

Giờ chúng ta sẽ thêm :admin column vào bảng User để cho phép Admin truy cập và tạo oauth_application cho Client.

rails g migration AddAdminToUser admin:boolean 

Chúng ta sẽ có một file mới db/migrate/20240725135643_add_admin_to_user.rb, chỉnh nội dung bên trong như sau.

# frozen_string_literal: true class AddAdminToUser < ActiveRecord::Migration[7.1] def change add_column :users, :admin, :boolean, default: false end
end

Sau đó run rails db:migration để cập nhật cấu trúc DB.

Chỉnh sử lại config một chút: PR's Commit

  • Sửa lại block admin_authenticator để cho kiểm tra xem người dùng hiện tại có phải là Admin hay không?
admin_authenticator do current_user.try(:admin) ? current_user : redirect_to(new_user_session_path)
end
  • Tạm thời chúng ta hãy bỏ qua màn hình authorization và không hiển thị nó cho resource owner khi họ đăng nhập hoặc uỷ quyền.
skip_authorization do |_resource_owner, _client| true
end

Giờ chúng ta sẽ tạo một API để lấy thông tin của resource_owner để test trong tương lai gần. Chi tiết xem trong phần commit này.

  • Về cơ bản API này sẽ trả về thông tin của người dùng dựa trên access_token nhận được.

Customize Doorkeeper UI and Testing

Trong phần này ngoài những thứ như:

  • Generate SIGNING_KEY: sử dụng để ký id_token trả về cho Client.
  • Update scopes: chúng ta sẽ có 3 loại scopes:
    • openid : cho biết Client đang sử dụng OpenID Connect để xác thực và yêu cầu thông tin sub từ Identity Provider.
    • email : thêm thông tin email của resource owner bên trong id_token trả về cho Client.
    • profile : thêm thông tin namephone_number bên trong id_token trả về cho Client.
  • Update code to allow skip authorization: thêm :skip_authorization vào trong bảng OauthApplication để cho phép Client bỏ qua màn hình authorization sau khi người dùng đăng nhập thành công.

Thì phần còn lại sẽ chủ yếu là sửa lại UI sử dụng Tailwind cho nó đẹp hơn chút, chi tiết chỉnh sửa các bạn có thể xem ở trong Pull Request này nhá.

Khởi động lại server và truy cập http://localhost:3000 chúng ta sẽ được giao diện như sau:

image.png

Hơi cùi một chút nhưng hiện tại hãy truy cập rails console để biến user đầu tiên thành Admin nào, tại chưa có chức năng tạo user cho admin 😂.

➜ stid_web git:(main) rails console
Loading development environment (Rails 7.1.3.4) [1] pry(main)> User.first.update(admin: true) User Load (0.2ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1 TRANSACTION (0.2ms) BEGIN TRANSACTION (0.1ms) COMMIT
=> true
[2] pry(main)> 

OK, giờ hãy click Applications -> chọn New Application và đăng ký các thông tin như hình dưới đây. image.png

  • Name: tên của Client, trong trường hợp này mình sẽ để là Study Together Client tuy nhiên bạn có thể đặt bất kỳ tên nào mình muốn.
  • Redirect URI: đây là đường dẫn để chúng ta nhận được authorization code trong OAuth2.0 sau khi người dùng đã ấn nút chấp nhận uỷ quyền sau khi đăng nhập.
  • Confidential: trong trường hợp này mình sẽ để ứng dụng thuộc dạng confidential, tích vào ô checkbox.
  • Skip authorization: trong trường hợp này mình sẽ chọn không, người dùng sẽ thấy màn hình authorization và quyết định xem có uỷ quyền cho ứng dụng hay không sau khi đăng nhập thành công.
  • Scopes: mình sẽ chỉ định cả 3 scopes mà ID Provider hỗ trợ, đó là openid email profile. Mỗi scope cách nhau bởi dấu space.

Submit và bạn sẽ được chuyển đến màn hình chi tiết của application để xem thông tin application vừa tạo.

image.png

Tiếp theo để test, hãy click vào nút Authorize như trong ảnh, và bạn sẽ thấy chúng ta được chuyển đến màn hình Authorization.

  • Do bạn đang đăng nhập nên sẽ không thấy bước yêu cầu đăng nhập.
  • Nếu bạn copy URL hiện tại, sau đó đăng xuất và truy cập lại vào URL vừa copy, bạn sẽ thấy màn hình đăng nhập sau đó mới đến màn hình Authorization.

image.png

Ở bước này, nếu bạn đồng ý uỷ quyền thì ID Provider sẽ redirect bạn về redirect_uri đã đăng ký trước đó kèm với authorization_code để bạn đổi lấy access_token trong tương lai (chúng ta sẽ tìm hiểu sâu thêm trong phần thiết lập Client).

  • Vì chưa có Client nào để test nên trước đó mình đã để giá trị của redirect_urihttp://localhost:3000 nên không có gì đặc biệt ở đây cả.

Conclusion

Như vậy là chúng ta đã cài đặt xong một phần của OAuth2.0 và OpenID Connect phía ID Provider bằng cách sử dụng doorkeeperdoorkeeper-openid_connect gems. Trong bài tiếp theo mình sẽ cùng các bạn tạo một Client, sau đó sử dụng ID Provider để đăng nhập vào Client nhé.

Link Github: https://github.com/learnforward2023/stid_web

Tài liệu tham khảo:

Again, nếu bạn thấy bài viết này hữu ích, hãy cho mình một upvote và follow để mình có thêm động lực viết những bài sau tốt và chất lượng hơn! Thank you!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Sử dụng Misoca API (oauth2) với Python

Với bài viết này giúp chúng ta có thể nắm được. ・Tìm hiểu cách xử lý API misoca bằng Python.

0 0 49

- vừa được xem lúc

OAuth là gì? Cách thức hoạt động của OAuth

. OAuth cho phép các trang web và dịch vụ chia sẻ tài nguyên giữa những người dùng. Nó được sử dụng rộng rãi, nhưng hãy lưu ý về các lỗ hổng của nó.

0 0 53

- vừa được xem lúc

Giải thích về cách thức hoạt động của OAuth 2.0

OAuth2.0 là một giao thức authorization, cho phép truy cập tài nguyên của resource owner bằng cách bật client applications trên các dịch vụ HTTP như Facebook, GitHub, v.

0 0 39

- vừa được xem lúc

OAuth là gì ? Nó hoạt động như thế nào ?

Mở đầu. Khi bạn vào một trang web muốn sử dụng các dịch vụ của một trang web khác — chẳng hạn như đăng nhập vào bằng tài khoản Facebook — thay vì yêu cầu bạn chia sẻ tài khoản Facebook của mình của mì

0 0 54

- vừa được xem lúc

Từ OAuth đến OpenID Connect

Khi sử dụng các ứng dụng như Draw.io hay một số ứng dụng, trang web nào đó, bạn đã bao giờ gặp thông báo yêu cầu cấp quyền truy cập đến Google drive chưa.

0 0 103

- vừa được xem lúc

Tìm hiểu đôi chút về OAuth2

Chắc hẳn một trong số các bạn cũng đã từng nghe qua khái niệm OAuth trước đây. Về cơ bản, OAuth là một phương thức xác thực giúp một ứng dụng bên thứ 3 có thể được ủy quyền bởi người dùng để truy cập

0 0 34