Slide 1

Slide 1 text

Warden the building block behind Devise Guru-SP Leonardo Tegon

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

consulting and software engineering careers.plataformatec.com.br

Slide 4

Slide 4 text

› bundle add devise › rails generate devise:install › rails generate devise User › rails db:migrate › rails server

Slide 5

Slide 5 text

e-mail confirmation password recovery account registration Devise account locking account tracking session timeout

Slide 6

Slide 6 text

authentication logout session management Warden e-mail confirmation password recovery account registration Devise account locking account tracking session timeout

Slide 7

Slide 7 text

Warden?

Slide 8

Slide 8 text

Warden?

Slide 9

Slide 9 text

“Warden is a Rack-based middleware, designed to provide a mechanism for authentication in Ruby web applications” - Warden’s GitHub Wiki

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Uma interface Ruby para servidores web

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Um middleware pode interromper uma requisição

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

› curl -i localhost:9292 HTTP/1.1 200 OK Content-Type: application/json Transfer-Encoding: chunked { "text": "Olar, Guru-SP!" } › rackup

Slide 34

Slide 34 text

Warden não lida com armazenamento de sessão

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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: "[email protected]", password: "123", name: "Bruce Wayne" }, { id: 2, email: "[email protected]", password: "321", name: "James Gordon" } ]

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Strategies

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

• 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

Slide 57

Slide 57 text

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)

Slide 58

Slide 58 text

# 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

Slide 59

Slide 59 text

# 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

Slide 60

Slide 60 text

# 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

Slide 61

Slide 61 text

# 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

Slide 62

Slide 62 text

# 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

Slide 63

Slide 63 text

# 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

Slide 64

Slide 64 text

# 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

Slide 65

Slide 65 text

# 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

Slide 66

Slide 66 text

# 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

Slide 67

Slide 67 text

# 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

Slide 68

Slide 68 text

# 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

Slide 69

Slide 69 text

# 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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

Failure applications

Slide 73

Slide 73 text

class FailureApp def call(env) status = 401 headers = { "Content-Type" => "application/json" } body = ["Deu ruim”] [status, headers, body] end end

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

› 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" }

Slide 81

Slide 81 text

› curl -i "localhost:9292? [email protected]&password=abc" HTTP/1.1 401 Unauthorized Content-Type: application/json Transfer-Encoding: chunked Deu ruim

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

› curl -i "localhost:9292? [email protected]&password=abc" HTTP/1.1 401 Unauthorized Content-Type: application/json Transfer-Encoding: chunked Deu ruim: Invalid email or password

Slide 90

Slide 90 text

bcrypt-ruby https://github.com/codahale/bcrypt-ruby

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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' } ]

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

› 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" }

Slide 102

Slide 102 text

Scopes

Slide 103

Slide 103 text

e-commerce

Slide 104

Slide 104 text

e-commerce search wishlist purchases customers

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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)

Slide 112

Slide 112 text

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)

Slide 113

Slide 113 text

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)

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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}

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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}

Slide 119

Slide 119 text

# 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

Slide 120

Slide 120 text

# logs everyone out env["warden"].logout # logs :default scope out env["warden"].logout(:default) # logs :customer out env[“warden"].logout(:customer) Utilizando scopes

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

Callbacks

Slide 123

Slide 123 text

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)

Slide 124

Slide 124 text

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=>"[email protected]", :password=>"$2a$10$eyNdqvloKRe0hlzS z5Q7VOSKTMQq9PpmegqgYNMBbe0.fRxVskP 76", :name=>"Bruce Wayne"}

Slide 125

Slide 125 text

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)

Slide 126

Slide 126 text

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=>#}

Slide 127

Slide 127 text

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)

Slide 128

Slide 128 text

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}

Slide 129

Slide 129 text

# 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

Slide 130

Slide 130 text

# 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

Slide 131

Slide 131 text

# 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

Slide 132

Slide 132 text

# 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

Slide 133

Slide 133 text

# 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

Slide 134

Slide 134 text

# 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

Slide 135

Slide 135 text

# 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

Slide 136

Slide 136 text

# 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

Slide 137

Slide 137 text

# 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

Slide 138

Slide 138 text

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)

Slide 139

Slide 139 text

on_request Warden::Manager.on_request do |warden| # do something end

Slide 140

Slide 140 text

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=>#}

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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" } }

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

before_failure Warden::Manager.before_failure do |env, opts| # do something end {:action=>"unauthenticated", :message=>"Invalid email or password", :attempted_path=>"/? [email protected]&password=1234"}

Slide 145

Slide 145 text

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|

Slide 146

Slide 146 text

# 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

Slide 147

Slide 147 text

# 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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

Revisão

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

Abstrações nos ajudam a desenvolver features ✨

Slide 157

Slide 157 text

É bom aprender como nossas ferramentas funcionam

Slide 158

Slide 158 text

Benefícios de saber como uma Gem funciona

Slide 159

Slide 159 text

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

Slide 160

Slide 160 text

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

Slide 161

Slide 161 text

Benefícios de saber como uma Gem funciona •Procurar bugs com mais confiança •Customizar com eficiência •Contribuir para o open source ❤

Slide 162

Slide 162 text

Obrigado! github.com/tegon twitter.com/tegonl speakerdeck.com/tegon Guru-SP