Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Warden - The building block behind Devise (Português)

Warden - The building block behind Devise (Português)

Praticamente toda aplicação web precisa de alguma forma de autenticação. Por isso, faz bastante sentido que existam bibliotecas para tomar conta disso, o que permite que os desenvolvedores foquem em outros problemas.
Você já deve ter ouvido falar, ou até mesmo já usou o Devise: basta instalar a gem e rodar alguns comandos, e você já tem um sistema de autenticação robusto.
O que muita gente não sabe é que por baixo dos panos o Devise utiliza uma outra gem: o Warden. Nessa palestra vou mostrar o que é o Warden, para que ele serve, e como o Devise o utiliza.

534f215d73d93dae9314db40d9746acd?s=128

Leonardo Tegon

August 18, 2018
Tweet

Transcript

  1. Warden the building block behind Devise Guru-SP Leonardo Tegon

  2. Leonardo Tegon github.com/tegon twitter.com/tegonl speakerdeck.com/tegon Guru-SP

  3. consulting and software engineering careers.plataformatec.com.br

  4. › bundle add devise › rails generate devise:install › rails

    generate devise User › rails db:migrate › rails server
  5. e-mail confirmation password recovery account registration Devise account locking account

    tracking session timeout
  6. authentication logout session management Warden e-mail confirmation password recovery account

    registration Devise account locking account tracking session timeout
  7. Warden?

  8. Warden?

  9. “Warden is a Rack-based middleware, designed to provide a mechanism

    for authentication in Ruby web applications” - Warden’s GitHub Wiki
  10. None
  11. Uma interface Ruby para servidores web

  12. GET https://github.com/plataformatec/devise

  13. GET https://github.com/plataformatec/devise

  14. GET https://github.com/plataformatec/devise

  15. GET https://github.com/plataformatec/devise

  16. GET https://github.com/plataformatec/devise

  17. GET https://github.com/plataformatec/devise

  18. class MyApp def call(env) status = 200 headers = {

    "Content-Type" => "application/json" } body = ['{ "text": "Olar, Guru-SP!" }'] [status, headers, body] end end run MyApp.new Aplicação Rack
  19. class MyApp def call(env) status = 200 headers = {

    "Content-Type" => "application/json" } body = ['{ "text": "Olar, Guru-SP!" }'] [status, headers, body] end end run MyApp.new { "rack.version"=>[1, 3], "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, "SCRIPT_NAME"=>"", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"puma 3.11.0 Love Song", "GATEWAY_INTERFACE"=>"CGI/1.2", "REQUEST_METHOD"=>"GET", "REQUEST_PATH"=>"/", "HTTP_VERSION"=>"HTTP/1.1", "HTTP_HOST"=>"localhost:9292", "HTTP_USER_AGENT"=>"curl/7.54.0", "HTTP_ACCEPT"=>"*/*", "SERVER_NAME"=>"localhost", "SERVER_PORT"=>"9292" } Aplicação Rack
  20. Middleware class MyMiddleware def initialize(app) @app = app end def

    call(env) # do something with env @app.call(env) end end
  21. Middleware class MyMiddleware def initialize(app) @app = app end def

    call(env) # do something with env @app.call(env) end end #<MyApp:0x007fb8972a39e0>
  22. GET https://github.com/plataformatec/devise

  23. Rails::Rack::Logger GET https://github.com/plataformatec/devise

  24. Rails::Rack::Logger ActionDispatch::Cookies GET https://github.com/plataformatec/devise

  25. Rails::Rack::Logger ActionDispatch::Cookies ActionDispatch::Flash GET https://github.com/plataformatec/devise

  26. Um middleware pode interromper uma requisição

  27. GET https://github.com/plataformatec/devise

  28. Rails::Rack::Logger GET https://github.com/plataformatec/devise

  29. Rails::Rack::Logger Warden::Manager GET https://github.com/plataformatec/devise

  30. Rails::Rack::Logger Warden::Manager GET https://github.com/plataformatec/devise 401 unauthorized

  31. Rails::Rack::Logger Warden::Manager GET https://github.com/plataformatec/devise 401 unauthorized

  32. Aplicação de exemplo # config.ru class MyApp def call(env) status

    = 200 headers = { "Content-Type" => "application/json" } body = ['{ "text": "Olar, Guru-SP!" }'] [status, headers, body] end end run MyApp.new
  33. › curl -i localhost:9292 HTTP/1.1 200 OK Content-Type: application/json Transfer-Encoding:

    chunked { "text": "Olar, Guru-SP!" } › rackup
  34. Warden não lida com armazenamento de sessão

  35. GET https://github.com/plataformatec/devise

  36. GET https://github.com/plataformatec/devise Rack::Session::Cookie

  37. GET https://github.com/plataformatec/devise Rack::Session::Cookie env["rack.session"]

  38. GET https://github.com/plataformatec/devise Rack::Session::Cookie Warden::Manager env["rack.session"]

  39. GET https://github.com/plataformatec/devise Rack::Session::Cookie Warden::Manager env["rack.session"] env["warden"]

  40. use Rack::Session::Cookie, secret: "warden" use Warden::Manager do |manager| manager.default_strategies :password

    manager.failure_app = FailureApp.new end run MyApp.new Configuração do Warden
  41. Serialização de sessão Warden::Manager.serialize_into_session do |user| user[:id] end Warden::Manager.serialize_from_session do

    |id| USERS.find { |user| user[:id] == id } end
  42. Serialização de sessão Warden::Manager.serialize_into_session do |user| user[:id] end Warden::Manager.serialize_from_session do

    |id| USERS.find { |user| user[:id] == id } end USERS = [ { id: 1, email: "bruce@wayne.com", password: "123", name: "Bruce Wayne" }, { id: 2, email: "gordon@gcpd.gov", password: "321", name: "James Gordon" } ]
  43. use Rack::Session::Cookie, secret: "warden" use Warden::Manager do |manager| manager.default_strategies :password

    manager.failure_app = FailureApp.new end run MyApp.new Configuração do Warden
  44. use Rack::Session::Cookie, secret: "warden" use Warden::Manager do |manager| manager.default_strategies :password

    manager.failure_app = FailureApp.new end run MyApp.new Configuração do Warden
  45. Strategies

  46. None
  47. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  48. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  49. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  50. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  51. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  52. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  53. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  54. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  55. Métodos disponíveis dentro de strategies • params – Parâmetros da

    requisição • request – Objeto da requisição (Rack::Request) • session – Sessão da requisição • env – Hash do Rack contendo os dados da requisição
  56. • success! – Indica que a autenticação deu certo (resulta

    em halt!) • fail! – Indica que a autenticação falhou (resulta em halt!) • halt! – Interrompe a execução • redirect! – Redireciona para outra URL (resulta em halt!) • custom! – Aceita um array no formato do Rack (resulta em halt!) • pass – Ignora a strategy (padrão) Métodos disponíveis dentro de strategies
  57. Utilizando uma strategy # authenticates with the default strategy env["warden"].authenticate

    # authenticates with the `password` strategy env["warden"].authenticate(:password) # try to authenticate with the `password` strategy. # If it fails, authenticate with the `api_token` strategy env["warden"].authenticate(:password, :api_token)
  58. # devise/lib/devise/strategies/rememberable.rb def valid? @remember_cookie = nil remember_cookie.present? end def

    authenticate! resource = mapping.to.serialize_from_cookie(*remember_cookie) unless resource cookies.delete(remember_key) return pass end if validate(resource) remember_me(resource) if extend_remember_me?(resource) resource.after_remembered success!(resource) end end devise sample
  59. # devise/lib/devise/strategies/rememberable.rb def valid? @remember_cookie = nil remember_cookie.present? end def

    authenticate! resource = mapping.to.serialize_from_cookie(*remember_cookie) unless resource cookies.delete(remember_key) return pass end if validate(resource) remember_me(resource) if extend_remember_me?(resource) resource.after_remembered success!(resource) end end devise sample
  60. # devise/lib/devise/strategies/rememberable.rb def valid? @remember_cookie = nil remember_cookie.present? end def

    authenticate! resource = mapping.to.serialize_from_cookie(*remember_cookie) unless resource cookies.delete(remember_key) return pass end if validate(resource) remember_me(resource) if extend_remember_me?(resource) resource.after_remembered success!(resource) end end devise sample
  61. # devise/lib/devise/strategies/rememberable.rb def valid? @remember_cookie = nil remember_cookie.present? end def

    authenticate! resource = mapping.to.serialize_from_cookie(*remember_cookie) unless resource cookies.delete(remember_key) return pass end if validate(resource) remember_me(resource) if extend_remember_me?(resource) resource.after_remembered success!(resource) end end devise sample
  62. # devise/lib/devise/strategies/rememberable.rb def valid? @remember_cookie = nil remember_cookie.present? end def

    authenticate! resource = mapping.to.serialize_from_cookie(*remember_cookie) unless resource cookies.delete(remember_key) return pass end if validate(resource) remember_me(resource) if extend_remember_me?(resource) resource.after_remembered success!(resource) end end devise sample
  63. # devise/lib/devise/strategies/rememberable.rb def valid? @remember_cookie = nil remember_cookie.present? end def

    authenticate! resource = mapping.to.serialize_from_cookie(*remember_cookie) unless resource cookies.delete(remember_key) return pass end if validate(resource) remember_me(resource) if extend_remember_me?(resource) resource.after_remembered success!(resource) end end devise sample
  64. # devise/lib/devise/strategies/database_authenticatable.rb def authenticate! resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash) hashed

    = false if validate(resource){ hashed = true; resource.valid_password?(password) } remember_me(resource) resource.after_database_authentication success!(resource) end mapping.to.new.password = password if !hashed && Devise.paranoid fail(:not_found_in_database) unless resource end devise sample
  65. # devise/lib/devise/strategies/database_authenticatable.rb def authenticate! resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash) hashed

    = false if validate(resource){ hashed = true; resource.valid_password?(password) } remember_me(resource) resource.after_database_authentication success!(resource) end mapping.to.new.password = password if !hashed && Devise.paranoid fail(:not_found_in_database) unless resource end devise sample
  66. # devise/lib/devise/strategies/database_authenticatable.rb def authenticate! resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash) hashed

    = false if validate(resource){ hashed = true; resource.valid_password?(password) } remember_me(resource) resource.after_database_authentication success!(resource) end mapping.to.new.password = password if !hashed && Devise.paranoid fail(:not_found_in_database) unless resource end devise sample
  67. # devise/lib/devise/strategies/database_authenticatable.rb def authenticate! resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash) hashed

    = false if validate(resource){ hashed = true; resource.valid_password?(password) } remember_me(resource) resource.after_database_authentication success!(resource) end mapping.to.new.password = password if !hashed && Devise.paranoid fail(:not_found_in_database) unless resource end devise sample
  68. # devise/lib/devise/strategies/database_authenticatable.rb def authenticate! resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash) hashed

    = false if validate(resource){ hashed = true; resource.valid_password?(password) } remember_me(resource) resource.after_database_authentication success!(resource) end mapping.to.new.password = password if !hashed && Devise.paranoid fail(:not_found_in_database) unless resource end devise sample
  69. # devise/lib/devise/strategies/database_authenticatable.rb def authenticate! resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash) hashed

    = false if validate(resource){ hashed = true; resource.valid_password?(password) } remember_me(resource) resource.after_database_authentication success!(resource) end mapping.to.new.password = password if !hashed && Devise.paranoid fail(:not_found_in_database) unless resource end devise sample
  70. use Rack::Session::Cookie, secret: "warden" use Warden::Manager do |manager| manager.default_strategies :password

    manager.failure_app = FailureApp.new end run MyApp.new Configuração do Warden
  71. use Rack::Session::Cookie, secret: "warden" use Warden::Manager do |manager| manager.default_strategies :password

    manager.failure_app = FailureApp.new end run MyApp.new Configuração do Warden
  72. Failure applications

  73. class FailureApp def call(env) status = 401 headers = {

    "Content-Type" => "application/json" } body = ["Deu ruim”] [status, headers, body] end end
  74. require "warden" class MyApp def call(env) env["warden"].authenticate! user = env["warden"].user

    status = 200 headers = { "Content-Type" => "application/json" } body = ["{ \"user\": \"#{user[:name]}\" }"] [status, headers, body] end end
  75. require "warden" class MyApp def call(env) env["warden"].authenticate! user = env["warden"].user

    status = 200 headers = { "Content-Type" => "application/json" } body = ["{ \"user\": \"#{user[:name]}\" }"] [status, headers, body] end end
  76. require "warden" class MyApp def call(env) env["warden"].authenticate! user = env["warden"].user

    status = 200 headers = { "Content-Type" => "application/json" } body = ["{ \"user\": \"#{user[:name]}\" }"] [status, headers, body] end end
  77. require "warden" class MyApp def call(env) env["warden"].authenticate! user = env["warden"].user

    status = 200 headers = { "Content-Type" => "application/json" } body = ["{ \"user\": \"#{user[:name]}\" }"] [status, headers, body] end end
  78. require "warden" class MyApp def call(env) env["warden"].authenticate! user = env["warden"].user

    status = 200 headers = { "Content-Type" => "application/json" } body = ["{ \"user\": \"#{user[:name]}\" }"] [status, headers, body] end end
  79. require "warden" class MyApp def call(env) env["warden"].authenticate! user = env["warden"].user

    status = 200 headers = { "Content-Type" => "application/json" } body = ["{ \"user\": \"#{user[:name]}\" }"] [status, headers, body] end end
  80. › curl -i "localhost:9292?email=bruce@wayne.com&password=123" HTTP/1.1 200 OK Content-Type: application/json Set-Cookie:

    rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRWE3ZjA3MTkzNTY2MG U1ZmE1MzZh%0AZWY5M2QzZmNjNTg1MTVhNmYwZmNmNDQyMzFiZWExNzNkOGZkND RiNmU2NTcG%0AOwBGSSIcd2FyZGVuLnVzZXIuZGVmYXVsdC5rZXkGOwBUaQY%3D %0A--ac7c6d8d93aba354ad6cff80c59ef0d1730f8938; path=/; HttpOnly Transfer-Encoding: chunked { "user": "Bruce Wayne" }
  81. › curl -i "localhost:9292? email=bruce@wayne.com&password=abc" HTTP/1.1 401 Unauthorized Content-Type: application/json

    Transfer-Encoding: chunked Deu ruim
  82. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  83. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end
  84. email & senha Warden::Strategies.add(:password) do def valid? params["email"] && params["password"]

    end def authenticate! user = USERS.find do |user| user[:email] == params["email"] && user[:password] == params["password"] end if user.nil? fail!("Invalid email or password") else success!(user) end end end env["warden"].message
  85. class FailureApp def call(env) error_message = env["warden"].message status = 401

    headers = { "Content-Type" => "application/json" } body = ["Deu ruim: #{error_message}"] [status, headers, body] end end
  86. class FailureApp def call(env) error_message = env["warden"].message status = 401

    headers = { "Content-Type" => "application/json" } body = ["Deu ruim: #{error_message}"] [status, headers, body] end end
  87. class FailureApp def call(env) error_message = env["warden"].message status = 401

    headers = { "Content-Type" => "application/json" } body = ["Deu ruim: #{error_message}"] [status, headers, body] end end
  88. class FailureApp def call(env) error_message = env["warden"].message status = 401

    headers = { "Content-Type" => "application/json" } body = ["Deu ruim: #{error_message}"] [status, headers, body] end end
  89. › curl -i "localhost:9292? email=bruce@wayne.com&password=abc" HTTP/1.1 401 Unauthorized Content-Type: application/json

    Transfer-Encoding: chunked Deu ruim: Invalid email or password
  90. bcrypt-ruby https://github.com/codahale/bcrypt-ruby

  91. Criptografando uma senha > BCrypt::Password.create("123") => "$2a$10$eyNdqvloKRe0hlzSz5Q7VOSKTMQq9PpmegqgYNMBb e0.fRxVskP76"

  92. USERS = [ { id: 1, email: 'bruce@wayne.com', password: '$2a$10$eyNdqvloKRe0hlzSz5Q7VOSKTMQq9PpmegqgYNMBbe0.fRxVskP76',

    name: 'Bruce Wayne' }, { id: 2, email: 'gordon@gcpd.gov', password: '$2a$10$alIdvOG32Z11Brn9XuX.FuDmoCyEQDLUQEWc/PdoaHvaGDjghp6ZO', name: 'James Gordon' } ]
  93. def authenticate! user = USERS.find { |user| user[:email] == params["email"]

    } return fail!("Invalid email or password") if user.nil? if BCrypt::Password.new(user[:password]).is_password?(params["password"]) success!(user) else fail!("Invalid email or password") end end
  94. def authenticate! user = USERS.find { |user| user[:email] == params["email"]

    } return fail!("Invalid email or password") if user.nil? if BCrypt::Password.new(user[:password]).is_password?(params["password"]) success!(user) else fail!("Invalid email or password") end end
  95. def authenticate! user = USERS.find { |user| user[:email] == params["email"]

    } return fail!("Invalid email or password") if user.nil? if BCrypt::Password.new(user[:password]).is_password?(params["password"]) success!(user) else fail!("Invalid email or password") end end
  96. def authenticate! user = USERS.find { |user| user[:email] == params["email"]

    } return fail!("Invalid email or password") if user.nil? if BCrypt::Password.new(user[:password]).is_password?(params["password"]) success!(user) else fail!("Invalid email or password") end end
  97. def authenticate! user = USERS.find { |user| user[:email] == params["email"]

    } return fail!("Invalid email or password") if user.nil? if BCrypt::Password.new(user[:password]).is_password?(params["password"]) success!(user) else fail!("Invalid email or password") end end
  98. def authenticate! user = USERS.find { |user| user[:email] == params["email"]

    } return fail!("Invalid email or password") if user.nil? if BCrypt::Password.new(user[:password]).is_password?(params["password"]) success!(user) else fail!("Invalid email or password") end end
  99. def authenticate! user = USERS.find { |user| user[:email] == params["email"]

    } return fail!("Invalid email or password") if user.nil? if BCrypt::Password.new(user[:password]).is_password?(params["password"]) success!(user) else fail!("Invalid email or password") end end
  100. def authenticate! user = USERS.find { |user| user[:email] == params["email"]

    } return fail!("Invalid email or password") if user.nil? if BCrypt::Password.new(user[:password]).is_password?(params["password"]) success!(user) else fail!("Invalid email or password") end end
  101. › curl -i "localhost:9292?email=bruce@wayne.com&password=123" HTTP/1.1 200 OK Content-Type: application/json Set-Cookie:

    rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRWRkZGY1NzBkMDM1 YTE5OTI0OGI5%0AMWU5NWM2NTdlZjAxN2QwZjE2NmNjZWU2YTNkOWFiZmI0Zj c5Y2E5MmI4OTAG%0AOwBGSSIcd2FyZGVuLnVzZXIuZGVmYXVsdC5rZXkGOwBU aQY%3D%0A--49fb8a3f9f887658cff35ee6c48d93618b044be3; path=/; HttpOnly Transfer-Encoding: chunked { "user": "Bruce Wayne" }
  102. Scopes

  103. e-commerce

  104. e-commerce search wishlist purchases customers

  105. e-commerce search wishlist purchases customers manage items promotions refunds editors

  106. Modelo User com muitas roles class User < ApplicationRecord has_many

    :roles
  107. Modelo User com muitas roles class User < ApplicationRecord has_many

    :roles
  108. Modelo User com muitas roles class User < ApplicationRecord has_many

    :roles def search_items(query)
  109. Modelo User com muitas roles class User < ApplicationRecord has_many

    :roles def search_items(query) def add_to_wishlist(item)
  110. Modelo User com muitas roles class User < ApplicationRecord has_many

    :roles def search_items(query) def add_to_wishlist(item) def purchase!(item)
  111. Modelo User com muitas roles class User < ApplicationRecord has_many

    :roles def search_items(query) def add_to_wishlist(item) def purchase!(item) def create_item(attributes)
  112. Modelo User com muitas roles class User < ApplicationRecord has_many

    :roles def search_items(query) def add_to_wishlist(item) def purchase!(item) def create_item(attributes) def promote_item(item)
  113. Modelo User com muitas roles class User < ApplicationRecord has_many

    :roles def search_items(query) def add_to_wishlist(item) def purchase!(item) def create_item(attributes) def promote_item(item) def refund_purchase(order)
  114. Modelo User com muitas roles class User < ApplicationRecord has_many

    :roles def search_items(query) def add_to_wishlist(item) def purchase!(item) def create_item(attributes) def promote_item(item) def refund_purchase(order) end
  115. Vários modelos com devise /editors/sign_in current_editor class Editor < ApplicationRecord

    devise :editors def create_item(attributes) def promote_item(item) def refund_purchase(order) end /customers/sign_in current_customer class Customer < ApplicationRecord devise :customers def search_items(query) def add_to_wishlist(item) def purchase!(item) end
  116. Vários modelos com devise /editors/sign_in current_editor class Editor < ApplicationRecord

    devise :editors def create_item(attributes) def promote_item(item) def refund_purchase(order) end /customers/sign_in current_customer class Customer < ApplicationRecord devise :customers def search_items(query) def add_to_wishlist(item) def purchase!(item) end env["rack.session"] => {"warden.user.editor.key"=>1}
  117. Vários modelos com devise /editors/sign_in current_editor class Editor < ApplicationRecord

    devise :editors def create_item(attributes) def promote_item(item) def refund_purchase(order) end /customers/sign_in current_customer class Customer < ApplicationRecord devise :customers def search_items(query) def add_to_wishlist(item) def purchase!(item) end
  118. Vários modelos com devise /editors/sign_in current_editor class Editor < ApplicationRecord

    devise :editors def create_item(attributes) def promote_item(item) def refund_purchase(order) end /customers/sign_in current_customer class Customer < ApplicationRecord devise :customers def search_items(query) def add_to_wishlist(item) def purchase!(item) end env["rack.session"] => {"warden.user.editor.key"=>1, "warden.user.customer.key"=>2}
  119. # authenticate the :customer scope with the :api_token strategy env["warden"].authenticate(:api_token,

    scope: :customer) # if no scope is passed, :default is used env["warden"].authenticated? # check the :customer scope env["warden"].authenticated?(:customer) # fetch the customer env[“warden"].user(:customer) Utilizando scopes
  120. # logs everyone out env["warden"].logout # logs :default scope out

    env["warden"].logout(:default) # logs :customer out env[“warden"].logout(:customer) Utilizando scopes
  121. Configuração de Scopes use Warden::Manager do |config| config.default_scope = :customer

    config.scope_defaults(:customer, strategies: [:password]) config.scope_defaults( :editor, store: false, strategies: [:editor, :account_owner] ) end
  122. Callbacks

  123. after_set_user Warden::Manager.after_set_user do |user, warden, opts| # do something end

    Causas env["warden"].user env["warden"].authenticate env["warden"].set_user(user)
  124. after_set_user Warden::Manager.after_set_user do |user, warden, opts| # do something end

    Causas env["warden"].user env["warden"].authenticate env["warden"].set_user(user) {:id=>1, :email=>"bruce@wayne.com", :password=>"$2a$10$eyNdqvloKRe0hlzS z5Q7VOSKTMQq9PpmegqgYNMBbe0.fRxVskP 76", :name=>"Bruce Wayne"}
  125. after_set_user Warden::Manager.after_set_user do |user, warden, opts| # do something end

    Causas env["warden"].user env["warden"].authenticate env["warden"].set_user(user)
  126. after_set_user Warden::Manager.after_set_user do |user, warden, opts| # do something end

    Causas env["warden"].user env["warden"].authenticate env["warden"].set_user(user) Warden::Proxy:70215393660020 @config={:default_scope=>:default, :scope_defaults=> {}, :default_strategies=>{:_all=>[:password]}, :inte rcept_401=>true, :failure_app=>#<FailureApp: 0x007fb8972a39e0>}
  127. after_set_user Warden::Manager.after_set_user do |user, warden, opts| # do something end

    Causas env["warden"].user env["warden"].authenticate env["warden"].set_user(user)
  128. after_set_user Warden::Manager.after_set_user do |user, warden, opts| # do something end

    Causas env["warden"].user env["warden"].authenticate env["warden"].set_user(user) {:store=>true, :event=>:authentication, :scope=>:default}
  129. # fetch env["warden"].user # authentication env["warden"].authenticate # set_user env["warden"].set_user(user) Warden::Manager.after_set_user

    except: :fetch Warden::Manager.after_set_user only: :authentication Warden::Manager.after_authentication Warden::Manager.after_set_user only: :fetch Warden::Manager.after_fetch Warden::Manager.after_set_user scope: :admin
  130. # devise/lib/devise/hooks/activatable.rb Warden::Manager.after_set_user do |record, warden, options| if record &&

    record.respond_to?(:active_for_authentication?) && ! record.active_for_authentication? scope = options[:scope] warden.logout(scope) throw :warden, scope: scope, message: record.inactive_message end end devise sample
  131. # devise/lib/devise/hooks/activatable.rb Warden::Manager.after_set_user do |record, warden, options| if record &&

    record.respond_to?(:active_for_authentication?) && ! record.active_for_authentication? scope = options[:scope] warden.logout(scope) throw :warden, scope: scope, message: record.inactive_message end end devise sample
  132. # devise/lib/devise/hooks/activatable.rb Warden::Manager.after_set_user do |record, warden, options| if record &&

    record.respond_to?(:active_for_authentication?) && ! record.active_for_authentication? scope = options[:scope] warden.logout(scope) throw :warden, scope: scope, message: record.inactive_message end end devise sample
  133. # devise/lib/devise/hooks/activatable.rb Warden::Manager.after_set_user do |record, warden, options| if record &&

    record.respond_to?(:active_for_authentication?) && ! record.active_for_authentication? scope = options[:scope] warden.logout(scope) throw :warden, scope: scope, message: record.inactive_message end end devise sample
  134. # devise/lib/devise/hooks/rememberable.rb Warden::Manager.after_set_user except: :fetch do |record, warden, options| scope

    = options[:scope] if record.respond_to?(:remember_me) && options[:store] != false && record.remember_me && warden.authenticated?(scope) Devise::Hooks::Proxy.new(warden).remember_me(record) end end devise sample
  135. # devise/lib/devise/hooks/rememberable.rb Warden::Manager.after_set_user except: :fetch do |record, warden, options| scope

    = options[:scope] if record.respond_to?(:remember_me) && options[:store] != false && record.remember_me && warden.authenticated?(scope) Devise::Hooks::Proxy.new(warden).remember_me(record) end end devise sample
  136. # devise/lib/devise/hooks/trackable.rb Warden::Manager.after_set_user except: :fetch do |record, warden, options| if

    record.respond_to?(:update_tracked_fields!) && warden.authenticated? (options[:scope]) && !warden.request.env['devise.skip_trackable'] record.update_tracked_fields!(warden.request) end end devise sample
  137. # devise/lib/devise/hooks/trackable.rb Warden::Manager.after_set_user except: :fetch do |record, warden, options| if

    record.respond_to?(:update_tracked_fields!) && warden.authenticated? (options[:scope]) && !warden.request.env['devise.skip_trackable'] record.update_tracked_fields!(warden.request) end end devise sample
  138. after_failed_fetch Warden::Manager.after_failed_fetch do |user, warden, opts| Warden::Manager.after_failed_fetch scope: :admin do

    |user, warden, opts| Causas # fails because the user isn't authenticated env["warden"].user env["warden"].user(:admin)
  139. on_request Warden::Manager.on_request do |warden| # do something end

  140. on_request Warden::Manager.on_request do |warden| # do something end Warden::Proxy:70215393660020 @config={:default_scope=>:default,

    :scope_defaults=> {}, :default_strategies=>{:_all=>[:password]}, :inte rcept_401=>true, :failure_app=>#<FailureApp: 0x007fb8972a39e0>}
  141. before_failure Warden::Manager.before_failure do |env, opts| # do something end

  142. before_failure Warden::Manager.before_failure do |env, opts| # do something end {

    "rack.version"=>[1, 3], "QUERY_STRING"=>"email=bruce@wayne.com&password=1234", "REQUEST_METHOD"=>"GET", "REQUEST_PATH"=>"/", "REQUEST_URI"=>"/?email=bruce@wayne.com&password=1234", "SERVER_NAME"=>"localhost", "SERVER_PORT"=>"9292", "PATH_INFO"=>"/unauthenticated", "warden.options"=> { :action=>"unauthenticated", :message=>"Invalid email or password", :attempted_path=>"/?email=bruce@wayne.com&password=1234" } }
  143. before_failure Warden::Manager.before_failure do |env, opts| # do something end

  144. before_failure Warden::Manager.before_failure do |env, opts| # do something end {:action=>"unauthenticated",

    :message=>"Invalid email or password", :attempted_path=>"/? email=bruce@wayne.com&password=1234"}
  145. before_logout Causas env["warden"].logout env["warden"].logout(:admin) Warden::Manager.before_logout do |user, warden, opts| Warden::Manager.before_logout

    scope: :admin do |user, warden, opts|
  146. # devise/lib/devise/hooks/forgetable.rb Warden::Manager.before_logout do |record, warden, options| if record.respond_to?(:forget_me!) Devise::Hooks::Proxy.new(warden).forget_me(record)

    end end devise sample
  147. # devise/lib/devise/hooks/forgetable.rb Warden::Manager.before_logout do |record, warden, options| if record.respond_to?(:forget_me!) Devise::Hooks::Proxy.new(warden).forget_me(record)

    end end devise sample
  148. Callbacks com prioridade Warden::Manager.prepend_before_logout do |user, warden, opts| # do

    something end
  149. Revisão

  150. Revisão • Warden é middleware de autenticação baseado em Rack

  151. Revisão • Warden é middleware de autenticação baseado em Rack

    • Uma strategy define a lógica para autenticar uma requisição
  152. Revisão • Warden é middleware de autenticação baseado em Rack

    • Uma strategy define a lógica para autenticar uma requisição • É possível configurar uma aplicação Rack para lidar com falhas de autenticação
  153. Revisão • Warden é middleware de autenticação baseado em Rack

    • Uma strategy define a lógica para autenticar uma requisição • É possível configurar uma aplicação Rack para lidar com falhas de autenticação • Scopes permitem que vários tipos de usuários estejam autenticados ao mesmo tempo
  154. Revisão • Warden é middleware de autenticação baseado em Rack

    • Uma strategy define a lógica para autenticar uma requisição • É possível configurar uma aplicação Rack para lidar com falhas de autenticação • Scopes permitem que vários tipos de usuários estejam autenticados ao mesmo tempo • Callbacks são executados em eventos de autenticação
  155. Revisão • Warden é middleware de autenticação baseado em Rack

    • Uma strategy define a lógica para autenticar uma requisição • É possível configurar uma aplicação Rack para lidar com falhas de autenticação • Scopes permitem que vários tipos de usuários estejam autenticados ao mesmo tempo • Callbacks são executados em eventos de autenticação • Exemplos do Devise
  156. Abstrações nos ajudam a desenvolver features ✨

  157. É bom aprender como nossas ferramentas funcionam

  158. Benefícios de saber como uma Gem funciona

  159. Benefícios de saber como uma Gem funciona •Procurar bugs com

    mais confiança
  160. Benefícios de saber como uma Gem funciona •Procurar bugs com

    mais confiança •Customizar com eficiência
  161. Benefícios de saber como uma Gem funciona •Procurar bugs com

    mais confiança •Customizar com eficiência •Contribuir para o open source ❤
  162. Obrigado! github.com/tegon twitter.com/tegonl speakerdeck.com/tegon Guru-SP