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

Warden: the building block behind Devise

Warden: the building block behind Devise

Authentication is one of the most common features of web applications, so it makes sense to have libraries that provide solutions for this problem. You've probably heard of or maybe used Devise: all you have to do is add it to your Gemfile and run a generator, and you have a robust authentication system.

Behind the scenes, Devise uses Warden to handle authentication. In this talk, I'll explain what Warden is, why it's useful and how Devise takes advantage of it to build the most popular authentication gem for Rails.

Leonardo Tegon

April 17, 2018
Tweet

More Decks by Leonardo Tegon

Other Decks in Programming

Transcript

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

    generate devise User › rails db:migrate › rails server
  2. authentication logout session management Warden e-mail confirmation password recovery account

    registration Devise account locking account tracking session timeout
  3. “Warden is a Rack-based middleware, designed to provide a mechanism

    for authentication in Ruby web applications” - Warden’s GitHub Wiki
  4. class MyApp def call(env) status = 200 headers = {

    "Content-Type" => "application/json" } body = ['{ "text": "Hello, RailsConf!" }'] [status, headers, body] end end run MyApp.new Rack application
  5. class MyApp def call(env) status = 200 headers = {

    "Content-Type" => "application/json" } body = ['{ "text": "Hello, RailsConf!" }'] [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" } Rack application
  6. Rack middleware class MyMiddleware def initialize(app) @app = app end

    def call(env) # do something with env @app.call(env) end end
  7. Rack middleware class MyMiddleware def initialize(app) @app = app end

    def call(env) # do something with env @app.call(env) end end #<MyApp:0x007fb8972a39e0>
  8. Rack app example # config.ru class MyApp def call(env) status

    = 200 headers = { "Content-Type" => "application/json" } body = ['{ "text": "Hello, RailsConf!" }'] [status, headers, body] end end run MyApp.new
  9. Session serialization 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: "[email protected]", password: "123", name: "Bruce Wayne" }, { id: 2, email: "[email protected]", password: "321", name: "James Gordon" } ]
  10. email & password strategy 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
  11. email & password strategy 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
  12. email & password strategy 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
  13. email & password strategy 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
  14. email & password strategy 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
  15. email & password strategy 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
  16. email & password strategy 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
  17. email & password strategy 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
  18. Methods available inside strategies • params – Request parameters •

    request – Rack::Request object • session – The session object for the request • env – Rack’s request environment hash
  19. • success! – Logs a user in (causes a halt!)

    • fail! – Sets the strategy to fail (causes a halt!) • halt! – Makes this one the last strategy processed • redirect! – Redirects to another url (causes a halt!) • custom! – Accepts a custom rack array (causes a halt!) • pass – Ignore this strategy (default) Methods available inside strategies
  20. Using a 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)
  21. # 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
  22. # 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
  23. # 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
  24. # 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
  25. # 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
  26. # 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
  27. # 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
  28. # 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
  29. # 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
  30. # 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
  31. # 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
  32. # 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
  33. class FailureApp def call(env) status = 401 headers = {

    "Content-Type" => "application/json" } body = ["Something went wrong"] [status, headers, body] end end
  34. Requiring authentication 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
  35. Requiring authentication 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
  36. Requiring authentication 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
  37. Requiring authentication 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
  38. Requiring authentication 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
  39. Requiring authentication 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
  40. › curl -i "localhost:[email protected]&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" }
  41. email & password strategy 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
  42. email & password strategy 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
  43. email & password strategy 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
  44. class FailureApp def call(env) error_message = env["warden"].message status = 401

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

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

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

    headers = { "Content-Type" => "application/json" } body = ["Something went wrong: #{error_message}"] [status, headers, body] end end
  48. USERS = [ { id: 1, email: '[email protected]', password: '$2a$10$eyNdqvloKRe0hlzSz5Q7VOSKTMQq9PpmegqgYNMBbe0.fRxVskP76',

    name: 'Bruce Wayne' }, { id: 2, email: '[email protected]', password: '$2a$10$alIdvOG32Z11Brn9XuX.FuDmoCyEQDLUQEWc/PdoaHvaGDjghp6ZO', name: 'James Gordon' } ]
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. › curl -i "localhost:[email protected]&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" }
  58. User model with many roles class User < ApplicationRecord has_many

    :roles def search_items(query) def add_to_wishlist(item)
  59. User model with many roles class User < ApplicationRecord has_many

    :roles def search_items(query) def add_to_wishlist(item) def purchase!(item)
  60. User model with many roles class User < ApplicationRecord has_many

    :roles def search_items(query) def add_to_wishlist(item) def purchase!(item) def create_item(attributes)
  61. User model with many 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)
  62. User model with many 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)
  63. User model with many 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
  64. Multiple models with 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
  65. Multiple models with 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}
  66. Multiple models with 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
  67. Multiple models with 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}
  68. # 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) Using scopes
  69. Using scopes # logs everyone out env["warden"].logout # logs :default

    scope out env["warden"].logout(:default) # logs :customer out env[“warden"].logout(:customer)
  70. Scope configuration 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
  71. after_set_user Warden::Manager.after_set_user do |user, warden, opts| # do something end

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

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

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

    Trigger actions 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>}
  75. after_set_user Warden::Manager.after_set_user do |user, warden, opts| # do something end

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

    Trigger actions env["warden"].user env["warden"].authenticate env["warden"].set_user(user) {:store=>true, :event=>:authentication, :scope=>:default}
  77. # 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
  78. # 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
  79. # 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
  80. # 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
  81. # 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
  82. # 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
  83. # 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
  84. # 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
  85. # 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
  86. after_failed_fetch Warden::Manager.after_failed_fetch do |user, warden, opts| Warden::Manager.after_failed_fetch scope: :admin do

    |user, warden, opts| Trigger actions # fails because the user isn't authenticated env["warden"].user env["warden"].user(:admin)
  87. 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>}
  88. before_failure Warden::Manager.before_failure do |env, opts| # do something end {

    "rack.version"=>[1, 3], "QUERY_STRING"=>"[email protected]&password=1234", "REQUEST_METHOD"=>"GET", "REQUEST_PATH"=>"/", "REQUEST_URI"=>"/[email protected]&password=1234", "SERVER_NAME"=>"localhost", "SERVER_PORT"=>"9292", "PATH_INFO"=>"/unauthenticated", "warden.options"=> { :action=>"unauthenticated", :message=>"Invalid email or password", :attempted_path=>"/[email protected]&password=1234" } }
  89. Review • Warden is a Rack-based middleware for authentication •

    A strategy holds the logic to authenticate a request
  90. Review • Warden is a Rack-based middleware for authentication •

    A strategy holds the logic to authenticate a request • A failure application is a Rack endpoint called after authentication failures
  91. Review • Warden is a Rack-based middleware for authentication •

    A strategy holds the logic to authenticate a request • A failure application is a Rack endpoint called after authentication failures • Scopes allow multiple type of users to be logged in at the same time
  92. Review • Warden is a Rack-based middleware for authentication •

    A strategy holds the logic to authenticate a request • A failure application is a Rack endpoint called after authentication failures • Scopes allow multiple type of users to be logged in at the same time • Callbacks are triggered on authentication events
  93. Review • Warden is a Rack-based middleware for authentication •

    A strategy holds the logic to authenticate a request • A failure application is a Rack endpoint called after authentication failures • Scopes allow multiple type of users to be logged in at the same time • Callbacks are triggered on authentication events • Devise examples
  94. Benefits of knowing how a Gem works •Chase down bugs

    with confidence •Customize their behavior efficiently
  95. Benefits of knowing how a Gem works •Chase down bugs

    with confidence •Customize their behavior efficiently •Contribute to open source ❤