Warden: the building block behind Devise
Tegon, Leonardo
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.
 Hi everyone, thank you all for joining me here. I really appreciate it. This is like my first RailsConf and also my first conference talk ever. So I hope you enjoy this. Thanks. And my name is Leonardo Tagon. But I think my mom is the only person that's still called by my first name. Most people call me Tagon, which is how you can find me on GitHub. I don't Twitter, I'm Tagon L. These slides are going to be at speaker deck slash Tagon. And this is also my first time in the US because I'm from Brazil. I live in Sao Paulo and I work at PlataFarm Attack. And this is a picture of our team. And if you don't know us, we are software development and AI consultancy. But you may have heard of us from our open source work. We are the ones behind the legacy language. And we also created some RubyGens, like simple form and device. So device is an authentication, right? And since authentication is a very common feature, it is good to have Gens that handle this for us. So we don't have to code it from scratch. So this is how it takes to have device working today. We just run a couple generators and that's it. But here's the thing. Those Gens, they can only get us to a certain point, right? So hopefully if your project succeeds, it's likely that you need different requirements for authentication. So when the time comes, it is good to know how the Gens we're using works so we can create the customizations we need without much trouble. For example, there was a project I worked on in which we had a token-based authentication for a JSON API. And this is something device doesn't support out of the box, right? So we ended up overriding the entire device that is controlled. And when we have specific requirements like this one, we can rely directly on our Gens features to build a custom authentication logic. So today, we're going to see how to do this and hopefully this can be useful for you in the future. So device has a lot of features, right? It does email confirmation, password recovery, account locking, tracking, and so on. But when we're talking about authentication, Wording is the one doing the job. So Wording is actually taking care of signing a user in or out and session management and so on. So what is this Wording thing? And when I hear the word, the first thing that comes to my head is this guy, right? The Wording of the North. But I'm not here to talk about Game of Thrones. I do love Game of Thrones, so if you want to talk about it, come find me after the talk. I'm here to talk about the Ruby Gen, right? So Wording is a rack middleware that provides a mechanism for authentication in Ruby web applications. And this is pretty much explaining what is Wording in a short way. But unless we know what a rack middleware is, it is hard to understand. So to make sure we're in the same page, I'm going to show you now what is rack. So rack is a Ruby interface for web service. And basically what it does is it unifies the API for both web servers and web frameworks in Ruby. So when we start a request from a browser to a Rails application, it is going to pass through a web server. So here we are using Puma. And Puma is going to pass this request down to a Rails application in a format that conforms with the rack API. And the same is true for the response. And since, and because, because rack unifies the request and response API, we can replace Puma here with Unicorn or maybe Passenger or something else. And we can also replace Rails with Hanami if you want to, or Sinatra. And everything still works because they are all communicating using the same API. So we're going to see now how we can implement a rack API in an application. So it's pretty easy to do it. We just create a class that responds to a method called and it's this method receives one argument. And from this method, we have to return an array with three items. Okay, so the first one is going to be our response status. The second one are response headers. And the third one are response body. And this is pretty much how it takes to implement the rack API. And this argument here is what we call the request environment hash. And it is a hash that contains all the information related to the current request. So we can see here we have server protocol, request method, request path, server part, and so on. And so I said in the beginning that wording is a rack middleware. And a middleware looks like a rack application. But here we are actually receiving a rack application instance. And this allows us inside the method cow to change the environment hash in some way and to pass this new version to a rack application. So a middleware can process the request in some way. And a request can be processed by multiple middlewares. So for example, when we start a request, it can pass to a logger middleware. And this middleware can read the environment file, environment hash, and log it to a file. And we have a cookies middleware that can add a set cookie header to the response. And we also have a middleware that handles flash message. And those are only some examples of middlewares in a Rails application. Okay, a middleware can also stop the request execution. So imagine when we start a request, we go back to our logger middleware, and now we have word as middleware. And at this point, Warden is going to say, hey, in order to continue, a user has to be authenticated. But let's say in this case it isn't. So we throw a 401 unauthorized and the request has halts. Okay, you can't pass. And the other middlewares down this tag are never going to be called. So back to our application example, you can see this, this isn't doing much right now. But the idea of this talk is as we go through Warden's concepts, we're going to implement in here. And by the end of it, we're going to have a simple authentication system working with Warden. So you can follow through with these slides later. Just remember to save this file, name it as config.ru. There is a comment there on the top. And to run a rack application, you can use the rackup command. And here we just send a request to make sure it's working. And one important thing to notice is that Warden doesn't handle session storage. So it has to run after a session middleware. So back to our request example, let's say we pass to a session cookie middleware. And this middleware is going to create an object on the environment hash called rack.session. And when the Warden middleware comes in, Warden is going to use this object to start a user in the session. And Warden is also going to provide us a object inside the environment hash. And this is what we're going to use to interact with Warden. So we're going to call to authenticate a user and to ask if a user is authenticated or not, all calling methods on this object. So to use Warden in a rack application, we have to do some setup. And first we have to say which middleware is we want to use. And so here we are just using the session cookie middleware and the Warden middleware. And it's important to say that the order here matters, right? So we have to declare Warden after the session cookie middleware. Another thing we have to do is we have to say how the user is going to be set allies into and from the session. So here we are just starting the user's ID. And we use the same ID later to find this user. And this user is here is just an array. We have two users here, Bruce Wayne and James Warden. You can also see I'm using plain text passwords here. I know this is not ideal, but it's just to keep the example simple enough. And if you go back to our setup, there's something here I didn't cover yet. This is the full strategy setting. So what are strategies? So basically a strategy is where we put the logic to authenticate a request. So if we have token authentication, maybe try to find a user for a given token or for a given email and check whether password is valid or not. And to this example, we are going to use a mail and password signing. And to declare a strategy, we call the method add on the Warden strategies class. And we pass as an argument a symbol to identify. And here we are calling it password. And we define two methods. So first we have valid. And the return value of valid will say whether a strategy should run or not. And if you don't define valid, the strategy will always run no matter what. Here we're just checking whether we have both parameters. And next we have authenticate. And here is actually where we try to find the user. Okay, so in our case, we just check if a user exists for both a mail and password. And if it does, we call success and pass the user as an argument. But if it doesn't, we just call fail and pass a invalid email or password message. So we saw there the parents method, we can also access the current request, the session and the environment hash inside the strategy. And we also use it success and fail. We can also call halt, which just stops the request execution, redirects, and custom. Custom actually accepts a rack array. But we saw that before right status headers and body. And we can also call pass, which just ignore the strategy. And this is the default behavior. So if you don't call any of the other previous methods, the strategy will be ignored. And to use a strategy, we just call authenticate on the warden object. And if you don't pass any arguments, the default strategy is going to be used. So for example, here is going to be password. But we can also explicitly say we want to authenticate using the password strategy. And we can also pass multiple strategies. And when that's the case, they're going to be calling cascade in order until one succeeds or fails or none are found relevant. So in this last example here, Warden is going to authenticate a user for the password strategy. But if we call fail or pass from it, it is going to use the API token strategy. So you can imagine now that in device we can log in as a user through database or a remember me cookie. And those are warden strategies, right? So I'm going to show the next. And first we have the remember me strategy. And here we just check whether the cook exists in the valid method. And on the authenticate method, we try to find the user for the remember me cookie. And if we do, we call success and pass the user as an argument. But if the user doesn't exist, we just return pass. And when we get to this pass here, the other device strategy is going to be called. And that is the one that tries to find a user for a database. So we call this method find for database authentication, which uses a authentication key like any mayor or username to try to find the user. And if the user exists, we check whether the password is valid or not. And if all of these works, we just call success. So we can see this is pretty much the same as what we did before. And if you go back to our setup, there's another thing I did in cover here, which is this failure app setting. So when an authentication fails, warden is going to call another rack application to handle. And this is what we call failure applications. So this is, there's, there's much to see here. This is just a regular rack application. We are just returning for zero one here. And in the body, we say something went wrong. So this is our setup now is complete. Now we can actually authenticate the user. So we can call this method authenticate up here. And this is going to authenticate the user. And we are using the bank version here, which means we want to throw a failure if the authentication fails. So our failure application gets called. And next, we just grab the user for the session by calling this dot user method. And in the body, we return the user's name to make sure it's working. Right. And if we send a request with a valid combination of email and password to our application now, we get our desired response, right? The user's blue swing. And we also have the set cookie header in the response, which is going to be used by the browsers to keep the user authenticated. But if we pass a wrong password, we're going to get our failure application response. So we have there for zero one and authorized and something went wrong. But if you go back to our strategy, when we're calling fail, we're actually providing a message here. And this gets started on the warden object under the message method. So we can use this in our in our field application, right? You can grab the message up here and we can return it on the body. And now if we test again, we have our strategies message. And so I said before that those plain text passwords were bad. So let's add some encryption, right? And to do this, we're going to use this big cryptee ruby. And this is pretty much easy to use. We just call the create method on the password class and passes a string. And this is the result for Bruce Wayne super secret password. And this is how our users array looks like now. But in order for this to work, we have to change our strategy. We have to perform an indication two steps now. This is like the device strategy. So we try to find the user for the email first. And if the user doesn't exist, we just fail right away. But if it does, we check whether the password is valid or not by calling this is password method from the big cryptee ruby gem. And if this returns true, we just call success as we did before. And if we test again, everything still works, except that for now we are using encrypted passwords. So this is pretty much all we have to see about strategies. You already know how we can implement a custom authentication logic. Now we're going to talk about another feature which is called scopes. And so we've been using scopes already. But to explain then, I want to talk to you about an e-commerce application. So imagine you have customers and they can search for items, add into a wish list and complete purchases. And we also have editors. They can manage those items. They can create promotions and pay me to refunds. And we have a requirement here that a person can be both a customer and an editor. So to solve this problem, we can have a user module with many roles. We create a customer role, an editor role, and this works. But we may end up with a big module that is responsible for too many things. So we have methods here that belong to customer and from editor. We could also create a single table inheritance here, but we may end up also with a table that has a lot of no columns. So for me, the ideal solution is to have different modules, different tables. And we know that this works with device, right? We can declare device in multiple modules. And it turns out that behind the scenes, device actually use a warning scope for each module. So what we pass to the device method is going to be a warning scope. And this is used in many places inside device. So for example, the signing path. You can see we have there slash editor slash sign in and slash customers slash sign in. And we also own the helper methods like current editor and current customer. And to warning, a scope is just a way to identify a user in the session. Okay, so if we sign in in this application as an editor, this is how our session looks like. We have warning.user.editor.key and the value one is the editor ID. And if we sign in in the same browser as a customer, this is how our session is going to look like. Now we have two keys in the session and they have different names and different values. So both users, they can coexist in the same session and share the same logic to sign in. And we can have different tables, different modules, and so on. And to use a scope, we don't have to configure anything. We just pass it as an option to the authenticate method. And if we don't pass any scopes, the default is going to be used. And we can pass scopes to multiple warning methods like authenticated when we ask if a user is authenticated or not. And when we are fetching the user from the session. And we can also pass a scope when we are logging the user out. And for the logout method, it is important to notice that if you don't pass any arguments, warning is going to log all the scopes out. So it's not going to just log the default scope out, it's going to log all of them. So we have to split to say which scope we want to log out. And, additionally, we can also add some configurations for its scope. So we can define which scope is going to be the default for our application. So we don't have to pass all the time in the warning methods. And we can also define the strategies we want to use for scope. And we can also say if your scope should be started on the session or not. So for the editor scope here, even when the user is signing, we're not going to start in the session. And this is pretty much all we have to see about scopes. Now we're going to see some callbacks. So Ward provides us some callbacks to the authentication events. And we're going to see which ones we have and how to use them. So first we have after set user. And this one is going to be called on three different situations. First, when we fetch the user from the session from the first time, by calling the user method. And when we authenticate the user. And when we set the user directly from the set user method. And as arguments, we receive the user, a warning instance, which is basically the same as the warning object in the environment hash. And also a hash of options. And those are options we can pass to the authenticate method. And since this can happen on multiple situations, we can pass some, a except or only options. And for the only option, we have some aliases. So we can declare a after authentication or after fetch callback. And we can also pass a scope. So this callback is used in many places inside device. I'm going to show some of them for you next. And so first, it is for the email confirmation feature. So when I use it right, when I use a sign in, we call this active for authentication method. And if the user's email isn't confirmed yet, this is going to return false. And when that's the case, we just log the user out. Another thing we do is for the remember me features. So right after user sign in, we just create a remember me cookie for this user. And we do this by calling this remember me method. There's a lot of logic there. I'm going to show this. But that's basically it. And we also have account tracking, right? So we have those columns, signing IP, signing count, and so on. So right after user sign in, we call this update track and fields. And we pass this request so we can extract an IP from it. Now, we also have an after field fetch callback. And this is going to be called when we try to find the user from the session, but the user isn't authenticated. And we have the same arguments as after set user. And we can also pass a scope. Next, we have our request. And this one is going to be called on each request right after wording is initialized. And as an argument, we receive the word in instance. Next, we have before failure. And this one is going to be called right after a field application when an authentication fails. And as arguments, we receive the environment hash. And also a hash of options. And this one can be useful if you need to change the environment hash in some way to provide more context to our failure application. And finally, we have before logout. And this one has the same arguments as we saw before. So user, a word in instance, and options. And we can also pass a scope. And device uses this for the remember me feature. So right after user logs out, we want to delete the remember me cooking. So we call this forget me method. And so those callbacks, they are stored in an array, which means they're going to be called in sequential order. So if you need to run a callback before the other ones, you can declare it by preficking its name with prepend underscore. So for example, here we have prepend before logout. And this works with any callbacks and the arguments and options stay the same. Okay. So a quick review of what we just seen today. We saw that wording is a rack based on middleware for authentication. It provides us some helpers to sign the user in, sign the user out, and to take care of session management and so on. And also, we saw how we can implement a custom authentication logic by using strategy. We saw how to handle authentication failures by using failure of the indications. And we also saw how we can have multiple type of users signed in the same session by using scopes. We saw some callbacks for the authentication events, how we can use them, and why they're useful. And between all that, we saw some device examples. Okay. So we have an idea of how this can be useful. And so device save us a lot of time by providing us features we have to write ourselves. Okay. And this is good because those abstractions, they help us move fast and watch more features for our clients. But if we ever get a chance, it is good to know how our tools work. And why is that? But first, because it's fun, right? And if that isn't enough, here are some benefits of knowing how a Jain works. And first, we can chase down bugs with more confidence. We can customize the behavior efficiently. So remember, in the beginning of the talk, I told you about the token-based authentication. Now we can imagine how we can implement this by creating a custom-oriented strategy, right? And this is, this can be pretty easy, way better than have to override the device sessions controller. And now that you know how Worden works, and you also have an idea of how device works, you can contribute to that, right? Because this is what Open Source is all about. So if you ever wanted to contribute to device, but you didn't know how to do it, come find me after the talk and I will help with everything you need. And I would love to accept your request from you. That is it. Thank you.
 Hi everyone, thank you all for joining me here. I really appreciate it. This is like my first  RailsConf and also my first conference talk ever. So I hope you enjoy this. Thanks. And my name is  Leonardo Tagon. But I think my mom is the only person that's still called by my first name.  Most people call me Tagon, which is how you can find me on GitHub. I don't Twitter, I'm Tagon L.  These slides are going to be at speaker deck slash Tagon. And this is also my first time in the  US because I'm from Brazil. I live in Sao Paulo and I work at PlataFarm Attack. And this is a  picture of our team. And if you don't know us, we are software development and AI consultancy.  But you may have heard of us from our open source work. We are the ones behind the legacy  language. And we also created some RubyGens, like simple form and device. So device is an  authentication, right? And since authentication is a very common feature, it is good to have  Gens that handle this for us. So we don't have to code it from scratch. So this is how it takes  to have device working today. We just run a couple generators and that's it. But here's the thing.  Those Gens, they can only get us to a certain point, right? So hopefully if your project succeeds,  it's likely that you need different requirements for authentication. So when the time comes,  it is good to know how the Gens we're using works so we can create the customizations we need  without much trouble. For example, there was a project I worked on in which we had a token-based  authentication for a JSON API. And this is something device doesn't support out of the box,  right? So we ended up overriding the entire device that is controlled. And when we have  specific requirements like this one, we can rely directly on our Gens features to build a custom  authentication logic. So today, we're going to see how to do this and hopefully this can be  useful for you in the future. So device has a lot of features, right? It does email confirmation,  password recovery, account locking, tracking, and so on. But when we're talking about authentication,  Wording is the one doing the job. So Wording is actually taking care of signing a user in or out  and session management and so on. So what is this Wording thing? And when I hear the word,  the first thing that comes to my head is this guy, right? The Wording of the North. But I'm not  here to talk about Game of Thrones. I do love Game of Thrones, so if you want to talk about it,  come find me after the talk. I'm here to talk about the Ruby Gen, right? So Wording is a rack  middleware that provides a mechanism for authentication in Ruby web applications. And this  is pretty much explaining what is Wording in a short way. But unless we know what a rack  middleware is, it is hard to understand. So to make sure we're in the same page, I'm going to show  you now what is rack. So rack is a Ruby interface for web service. And basically what it does is  it unifies the API for both web servers and web frameworks in Ruby. So when we start a request  from a browser to a Rails application, it is going to pass through a web server. So here we are  using Puma. And Puma is going to pass this request down to a Rails application in a format that  conforms with the rack API. And the same is true for the response. And since, and because, because  rack unifies the request and response API, we can replace Puma here with Unicorn or maybe  Passenger or something else. And we can also replace Rails with Hanami if you want to, or  Sinatra. And everything still works because they are all communicating using the same API. So we're  going to see now how we can implement a rack API in an application. So it's pretty easy to do it. We  just create a class that responds to a method called and it's this method receives one argument. And  from this method, we have to return an array with three items. Okay, so the first one is going to be  our response status. The second one are response headers. And the third one are response body. And  this is pretty much how it takes to implement the rack API. And this argument here is what we call  the request environment hash. And it is a hash that contains all the information related to the  current request. So we can see here we have server protocol, request method, request path, server part,  and so on. And so I said in the beginning that wording is a rack middleware. And a middleware  looks like a rack application. But here we are actually receiving a rack application instance. And  this allows us inside the method cow to change the environment hash in some way and to pass this  new version to a rack application. So a middleware can process the request in some way. And a request  can be processed by multiple middlewares. So for example, when we start a request, it can pass to  a logger middleware. And this middleware can read the environment file, environment hash, and log  it to a file. And we have a cookies middleware that can add a set cookie header to the response. And  we also have a middleware that handles flash message. And those are only some examples of  middlewares in a Rails application. Okay, a middleware can also stop the request execution. So  imagine when we start a request, we go back to our logger middleware, and now we have word as  middleware. And at this point, Warden is going to say, hey, in order to continue, a user has to be  authenticated. But let's say in this case it isn't. So we throw a 401 unauthorized and the request  has halts. Okay, you can't pass. And the other middlewares down this tag are never going to be  called. So back to our application example, you can see this, this isn't doing much right now. But  the idea of this talk is as we go through Warden's concepts, we're going to implement in here. And  by the end of it, we're going to have a simple authentication system working with Warden. So  you can follow through with these slides later. Just remember to save this file, name it as  config.ru. There is a comment there on the top. And to run a rack application, you can use the  rackup command. And here we just send a request to make sure it's working. And one important thing  to notice is that Warden doesn't handle session storage. So it has to run after a session middleware.  So back to our request example, let's say we pass to a session cookie middleware. And this middleware  is going to create an object on the environment hash called rack.session. And when the Warden  middleware comes in, Warden is going to use this object to start a user in the session. And Warden  is also going to provide us a object inside the environment hash. And this is what we're going  to use to interact with Warden. So we're going to call to authenticate a user and to ask if a user  is authenticated or not, all calling methods on this object. So to use Warden in a rack application,  we have to do some setup. And first we have to say which middleware is we want to use. And so here  we are just using the session cookie middleware and the Warden middleware. And it's important to  say that the order here matters, right? So we have to declare Warden after the session cookie  middleware. Another thing we have to do is we have to say how the user is going to be set  allies into and from the session. So here we are just starting the user's ID. And we use the  same ID later to find this user. And this user is here is just an array. We have two users here,  Bruce Wayne and James Warden. You can also see I'm using plain text passwords here. I know this  is not ideal, but it's just to keep the example simple enough. And if you go back to our setup,  there's something here I didn't cover yet. This is the full strategy setting. So what are strategies?  So basically a strategy is where we put the logic to authenticate a request. So if we have token  authentication, maybe try to find a user for a given token or for a given email and check whether  password is valid or not. And to this example, we are going to use a mail and password signing. And  to declare a strategy, we call the method add on the Warden strategies class. And we pass as an  argument a symbol to identify. And here we are calling it password. And we define two methods. So  first we have valid. And the return value of valid will say whether a strategy should run or not.  And if you don't define valid, the strategy will always run no matter what. Here we're just checking  whether we have both parameters. And next we have authenticate. And here is actually where we try to  find the user. Okay, so in our case, we just check if a user exists for both a mail and password.  And if it does, we call success and pass the user as an argument. But if it doesn't, we just call  fail and pass a invalid email or password message. So we saw there the parents method, we can also  access the current request, the session and the environment hash inside the strategy. And we also  use it success and fail. We can also call halt, which just stops the request execution, redirects,  and custom. Custom actually accepts a rack array. But we saw that before right status headers and  body. And we can also call pass, which just ignore the strategy. And this is the default behavior. So  if you don't call any of the other previous methods, the strategy will be ignored. And to use a strategy,  we just call authenticate on the warden object. And if you don't pass any arguments, the default  strategy is going to be used. So for example, here is going to be password. But we can also  explicitly say we want to authenticate using the password strategy. And we can also pass multiple  strategies. And when that's the case, they're going to be calling cascade in order until one  succeeds or fails or none are found relevant. So in this last example here, Warden is going to  authenticate a user for the password strategy. But if we call fail or pass from it, it is going to  use the API token strategy. So you can imagine now that in device we can log in as a user through  database or a remember me cookie. And those are warden strategies, right? So I'm going to show  the next. And first we have the remember me strategy. And here we just check whether the  cook exists in the valid method. And on the authenticate method, we try to find the user for  the remember me cookie. And if we do, we call success and pass the user as an argument. But  if the user doesn't exist, we just return pass. And when we get to this pass here, the other device  strategy is going to be called. And that is the one that tries to find a user for a database. So  we call this method find for database authentication, which uses a authentication key like any  mayor or username to try to find the user. And if the user exists, we check whether the password  is valid or not. And if all of these works, we just call success. So we can see this is pretty  much the same as what we did before. And if you go back to our setup, there's another thing  I did in cover here, which is this failure app setting. So when an authentication fails,  warden is going to call another rack application to handle. And this is what we call failure  applications. So this is, there's, there's much to see here. This is just a regular  rack application. We are just returning for zero one here. And in the body, we say something  went wrong. So this is our setup now is complete. Now we can actually authenticate the user.  So we can call this method authenticate up here. And this is going to authenticate the user. And  we are using the bank version here, which means we want to throw a failure if the authentication  fails. So our failure application gets called. And next, we just grab the user for the session  by calling this dot user method. And in the body, we return the user's name to make sure it's working.  Right. And if we send a request with a valid combination of email and password to our application  now, we get our desired response, right? The user's blue swing. And we also have the set cookie  header in the response, which is going to be used by the browsers to keep the user authenticated.  But if we pass a wrong password, we're going to get our failure application response. So we have  there for zero one and authorized and something went wrong. But if you go back to our strategy,  when we're calling fail, we're actually providing a message here. And this gets started on the  warden object under the message method. So we can use this in our in our field application, right?  You can grab the message up here and we can return it on the body. And now if we test again,  we have our strategies message. And so I said before that those plain text passwords were  bad. So let's add some encryption, right? And to do this, we're going to use this big cryptee  ruby. And this is pretty much easy to use. We just call the create method on the password class  and passes a string. And this is the result for Bruce Wayne super secret password. And this is how  our users array looks like now. But in order for this to work, we have to change our strategy.  We have to perform an indication two steps now. This is like the device strategy. So we try to  find the user for the email first. And if the user doesn't exist, we just fail right away.  But if it does, we check whether the password is valid or not by calling this is password method  from the big cryptee ruby gem. And if this returns true, we just call success as we did before.  And if we test again, everything still works, except that for now we are using encrypted passwords.  So this is pretty much all we have to see about strategies. You already know how we can  implement a custom authentication logic. Now we're going to talk about another feature which is called  scopes. And so we've been using scopes already. But to explain then, I want to talk to you about  an e-commerce application. So imagine you have customers and they can search for items, add  into a wish list and complete purchases. And we also have editors. They can manage those items.  They can create promotions and pay me to refunds. And we have a requirement here that a person can  be both a customer and an editor. So to solve this problem, we can have a user module with many  roles. We create a customer role, an editor role, and this works. But we may end up with a big  module that is responsible for too many things. So we have methods here that belong to customer and  from editor. We could also create a single table inheritance here, but we may end up also with  a table that has a lot of no columns. So for me, the ideal solution is to have different modules,  different tables. And we know that this works with device, right? We can declare device in multiple  modules. And it turns out that behind the scenes, device actually use a warning scope for each  module. So what we pass to the device method is going to be a warning scope. And this is used in  many places inside device. So for example, the signing path. You can see we have there slash  editor slash sign in and slash customers slash sign in. And we also own the helper methods like  current editor and current customer. And to warning, a scope is just a way to identify a user in  the session. Okay, so if we sign in in this application as an editor, this is how our session  looks like. We have warning.user.editor.key and the value one is the editor ID. And if we sign in in  the same browser as a customer, this is how our session is going to look like. Now we have two  keys in the session and they have different names and different values. So both users, they can  coexist in the same session and share the same logic to sign in. And we can have different tables,  different modules, and so on. And to use a scope, we don't have to configure anything. We just pass  it as an option to the authenticate method. And if we don't pass any scopes, the default is going  to be used. And we can pass scopes to multiple warning methods like authenticated when we ask  if a user is authenticated or not. And when we are fetching the user from the session. And we can  also pass a scope when we are logging the user out. And for the logout method, it is important to  notice that if you don't pass any arguments, warning is going to log all the scopes out. So it's not  going to just log the default scope out, it's going to log all of them. So we have to split to  say which scope we want to log out. And, additionally, we can also add some configurations for  its scope. So we can define which scope is going to be the default for our application. So we don't  have to pass all the time in the warning methods. And we can also define the strategies we want to  use for scope. And we can also say if your scope should be started on the session or not. So for  the editor scope here, even when the user is signing, we're not going to start in the session.  And this is pretty much all we have to see about scopes. Now we're going to see some callbacks.  So Ward provides us some callbacks to the authentication events. And we're going to see  which ones we have and how to use them. So first we have after set user. And this one is going to  be called on three different situations. First, when we fetch the user from the session from the  first time, by calling the user method. And when we authenticate the user. And when we set the  user directly from the set user method. And as arguments, we receive the user, a warning instance,  which is basically the same as the warning object in the environment hash. And also a hash of options.  And those are options we can pass to the authenticate method. And since this can happen  on multiple situations, we can pass some, a except or only options. And for the only option,  we have some aliases. So we can declare a after authentication or after fetch callback. And we  can also pass a scope. So this callback is used in many places inside device. I'm going to show  some of them for you next. And so first, it is for the email confirmation feature. So when I use  it right, when I use a sign in, we call this active for authentication method. And if the user's  email isn't confirmed yet, this is going to return false. And when that's the case, we just log the  user out. Another thing we do is for the remember me features. So right after user sign in, we just  create a remember me cookie for this user. And we do this by calling this remember me method.  There's a lot of logic there. I'm going to show this. But that's basically it. And we also have  account tracking, right? So we have those columns, signing IP, signing count, and so on. So right after  user sign in, we call this update track and fields. And we pass this request so we can  extract an IP from it. Now, we also have an after field fetch callback. And this is going to be  called when we try to find the user from the session, but the user isn't authenticated.  And we have the same arguments as after set user. And we can also pass a scope.  Next, we have our request. And this one is going to be called on each request right after wording  is initialized. And as an argument, we receive the word in instance. Next, we have before failure.  And this one is going to be called right after a field application when an authentication fails.  And as arguments, we receive the environment hash. And also a hash of options. And this one can be  useful if you need to change the environment hash in some way to provide more context to our  failure application. And finally, we have before logout. And this one has the same arguments as  we saw before. So user, a word in instance, and options. And we can also pass a scope. And device  uses this for the remember me feature. So right after user logs out, we want to delete the  remember me cooking. So we call this forget me method. And so those callbacks, they are  stored in an array, which means they're going to be called in sequential order. So if you need to  run a callback before the other ones, you can declare it by preficking its name with prepend  underscore. So for example, here we have prepend before logout. And this works with any callbacks  and the arguments and options stay the same. Okay. So a quick review of what we just seen today.  We saw that wording is a rack based on middleware for authentication. It provides us some helpers  to sign the user in, sign the user out, and to take care of session management and so on.  And also, we saw how we can implement a custom authentication logic by using strategy. We saw  how to handle authentication failures by using failure of the indications. And we also saw how  we can have multiple type of users signed in the same session by using scopes. We saw some callbacks  for the authentication events, how we can use them, and why they're useful. And between all that,  we saw some device examples. Okay. So we have an idea of how this can be useful.  And so device save us a lot of time by providing us features we have to write ourselves. Okay.  And this is good because those abstractions, they help us move fast and watch more features  for our clients. But if we ever get a chance, it is good to know how our tools work. And why is that?  But first, because it's fun, right? And if that isn't enough, here are some benefits of knowing  how a Jain works. And first, we can chase down bugs with more confidence. We can customize the  behavior efficiently. So remember, in the beginning of the talk, I told you about the token-based  authentication. Now we can imagine how we can implement this by creating a custom-oriented  strategy, right? And this is, this can be pretty easy, way better than have to override the device  sessions controller. And now that you know how Worden works, and you also have an idea of how  device works, you can contribute to that, right? Because this is what Open Source is all about.  So if you ever wanted to contribute to device, but you didn't know how to do it,  come find me after the talk and I will help with everything you need. And I would love to accept  your request from you. That is it. Thank you.
Warden_0_155.png Warden_156_1163.png Warden_1164_2003.png Warden_2004_3359.png Warden_3360_3851.png Warden_3852_4763.png Warden_4764_8435.png Warden_8436_8543.png Warden_8544_9671.png Warden_9672_10067.png Warden_10068_11423.png Warden_11424_11711.png Warden_11712_12059.png Warden_12060_12239.png Warden_12240_12611.png Warden_12612_13967.png Warden_13968_14867.png Warden_14868_17507.png Warden_17508_17711.png Warden_17712_18791.png Warden_18792_19523.png Warden_19524_19967.png Warden_19968_20363.png Warden_20364_20603.png Warden_20604_21455.png Warden_21456_22199.png Warden_22200_22247.png Warden_22248_22319.png Warden_22320_23495.png Warden_23496_27323.png Warden_27324_27575.png Warden_27576_33107.png Warden_33108_34667.png Warden_34668_36071.png Warden_36072_36227.png Warden_36228_36395.png Warden_36396_36489.png
Hi everyone, thank you all for joining me here. I really appreciate it. This is like my first RailsConf and also my first conference talk ever. So I hope you enjoy this. Thanks. And my name is Leonardo Tagon. But I think my mom is the only person that's still called by my first name. Most people call me Tagon, which is how you can find me on GitHub. I don't Twitter, I'm Tagon L. These slides are going to be at speaker deck slash Tagon. And this is also my first time in the US because I'm from Brazil. I live in Sao Paulo and I work at PlataFarm Attack. And this is a picture of our team. And if you don't know us, we are software development and AI consultancy. But you may have heard of us from our open source work. We are the ones behind the legacy language. And we also created some RubyGens, like simple form and device. So device is an authentication, right? And since authentication is a very common feature, it is good to have Gens that handle this for us. So we don't have to code it from scratch. So this is how it takes to have device working today. We just run a couple generators and that's it. But here's the thing. Those Gens, they can only get us to a certain point, right? So hopefully if your project succeeds, it's likely that you need different requirements for authentication. So when the time comes, it is good to know how the Gens we're using works so we can create the customizations we need without much trouble. For example, there was a project I worked on in which we had a token-based authentication for a JSON API. And this is something device doesn't support out of the box, right? So we ended up overriding the entire device that is controlled. And when we have specific requirements like this one, we can rely directly on our Gens features to build a custom authentication logic. So today, we're going to see how to do this and hopefully this can be useful for you in the future. So device has a lot of features, right? It does email confirmation, password recovery, account locking, tracking, and so on. But when we're talking about authentication, Wording is the one doing the job. So Wording is actually taking care of signing a user in or out and session management and so on. So what is this Wording thing? And when I hear the word, the first thing that comes to my head is this guy, right? The Wording of the North. But I'm not here to talk about Game of Thrones. I do love Game of Thrones, so if you want to talk about it, come find me after the talk. I'm here to talk about the Ruby Gen, right? So Wording is a rack middleware that provides a mechanism for authentication in Ruby web applications. And this is pretty much explaining what is Wording in a short way. But unless we know what a rack middleware is, it is hard to understand. So to make sure we're in the same page, I'm going to show you now what is rack. So rack is a Ruby interface for web service. And basically what it does is it unifies the API for both web servers and web frameworks in Ruby. So when we start a request from a browser to a Rails application, it is going to pass through a web server. So here we are using Puma. And Puma is going to pass this request down to a Rails application in a format that conforms with the rack API. And the same is true for the response. And since, and because, because rack unifies the request and response API, we can replace Puma here with Unicorn or maybe Passenger or something else. And we can also replace Rails with Hanami if you want to, or Sinatra. And everything still works because they are all communicating using the same API. So we're going to see now how we can implement a rack API in an application. So it's pretty easy to do it. We just create a class that responds to a method called and it's this method receives one argument. And from this method, we have to return an array with three items. Okay, so the first one is going to be our response status. The second one are response headers. And the third one are response body. And this is pretty much how it takes to implement the rack API. And this argument here is what we call the request environment hash. And it is a hash that contains all the information related to the current request. So we can see here we have server protocol, request method, request path, server part, and so on. And so I said in the beginning that wording is a rack middleware. And a middleware looks like a rack application. But here we are actually receiving a rack application instance. And this allows us inside the method cow to change the environment hash in some way and to pass this new version to a rack application. So a middleware can process the request in some way. And a request can be processed by multiple middlewares. So for example, when we start a request, it can pass to a logger middleware. And this middleware can read the environment file, environment hash, and log it to a file. And we have a cookies middleware that can add a set cookie header to the response. And we also have a middleware that handles flash message. And those are only some examples of middlewares in a Rails application. Okay, a middleware can also stop the request execution. So imagine when we start a request, we go back to our logger middleware, and now we have word as middleware. And at this point, Warden is going to say, hey, in order to continue, a user has to be authenticated. But let's say in this case it isn't. So we throw a 401 unauthorized and the request has halts. Okay, you can't pass. And the other middlewares down this tag are never going to be called. So back to our application example, you can see this, this isn't doing much right now. But the idea of this talk is as we go through Warden's concepts, we're going to implement in here. And by the end of it, we're going to have a simple authentication system working with Warden. So you can follow through with these slides later. Just remember to save this file, name it as config.ru. There is a comment there on the top. And to run a rack application, you can use the rackup command. And here we just send a request to make sure it's working. And one important thing to notice is that Warden doesn't handle session storage. So it has to run after a session middleware. So back to our request example, let's say we pass to a session cookie middleware. And this middleware is going to create an object on the environment hash called rack.session. And when the Warden middleware comes in, Warden is going to use this object to start a user in the session. And Warden is also going to provide us a object inside the environment hash. And this is what we're going to use to interact with Warden. So we're going to call to authenticate a user and to ask if a user is authenticated or not, all calling methods on this object. So to use Warden in a rack application, we have to do some setup. And first we have to say which middleware is we want to use. And so here we are just using the session cookie middleware and the Warden middleware. And it's important to say that the order here matters, right? So we have to declare Warden after the session cookie middleware. Another thing we have to do is we have to say how the user is going to be set allies into and from the session. So here we are just starting the user's ID. And we use the same ID later to find this user. And this user is here is just an array. We have two users here, Bruce Wayne and James Warden. You can also see I'm using plain text passwords here. I know this is not ideal, but it's just to keep the example simple enough. And if you go back to our setup, there's something here I didn't cover yet. This is the full strategy setting. So what are strategies? So basically a strategy is where we put the logic to authenticate a request. So if we have token authentication, maybe try to find a user for a given token or for a given email and check whether password is valid or not. And to this example, we are going to use a mail and password signing. And to declare a strategy, we call the method add on the Warden strategies class. And we pass as an argument a symbol to identify. And here we are calling it password. And we define two methods. So first we have valid. And the return value of valid will say whether a strategy should run or not. And if you don't define valid, the strategy will always run no matter what. Here we're just checking whether we have both parameters. And next we have authenticate. And here is actually where we try to find the user. Okay, so in our case, we just check if a user exists for both a mail and password. And if it does, we call success and pass the user as an argument. But if it doesn't, we just call fail and pass a invalid email or password message. So we saw there the parents method, we can also access the current request, the session and the environment hash inside the strategy. And we also use it success and fail. We can also call halt, which just stops the request execution, redirects, and custom. Custom actually accepts a rack array. But we saw that before right status headers and body. And we can also call pass, which just ignore the strategy. And this is the default behavior. So if you don't call any of the other previous methods, the strategy will be ignored. And to use a strategy, we just call authenticate on the warden object. And if you don't pass any arguments, the default strategy is going to be used. So for example, here is going to be password. But we can also explicitly say we want to authenticate using the password strategy. And we can also pass multiple strategies. And when that's the case, they're going to be calling cascade in order until one succeeds or fails or none are found relevant. So in this last example here, Warden is going to authenticate a user for the password strategy. But if we call fail or pass from it, it is going to use the API token strategy. So you can imagine now that in device we can log in as a user through database or a remember me cookie. And those are warden strategies, right? So I'm going to show the next. And first we have the remember me strategy. And here we just check whether the cook exists in the valid method. And on the authenticate method, we try to find the user for the remember me cookie. And if we do, we call success and pass the user as an argument. But if the user doesn't exist, we just return pass. And when we get to this pass here, the other device strategy is going to be called. And that is the one that tries to find a user for a database. So we call this method find for database authentication, which uses a authentication key like any mayor or username to try to find the user. And if the user exists, we check whether the password is valid or not. And if all of these works, we just call success. So we can see this is pretty much the same as what we did before. And if you go back to our setup, there's another thing I did in cover here, which is this failure app setting. So when an authentication fails, warden is going to call another rack application to handle. And this is what we call failure applications. So this is, there's, there's much to see here. This is just a regular rack application. We are just returning for zero one here. And in the body, we say something went wrong. So this is our setup now is complete. Now we can actually authenticate the user. So we can call this method authenticate up here. And this is going to authenticate the user. And we are using the bank version here, which means we want to throw a failure if the authentication fails. So our failure application gets called. And next, we just grab the user for the session by calling this dot user method. And in the body, we return the user's name to make sure it's working. Right. And if we send a request with a valid combination of email and password to our application now, we get our desired response, right? The user's blue swing. And we also have the set cookie header in the response, which is going to be used by the browsers to keep the user authenticated. But if we pass a wrong password, we're going to get our failure application response. So we have there for zero one and authorized and something went wrong. But if you go back to our strategy, when we're calling fail, we're actually providing a message here. And this gets started on the warden object under the message method. So we can use this in our in our field application, right? You can grab the message up here and we can return it on the body. And now if we test again, we have our strategies message. And so I said before that those plain text passwords were bad. So let's add some encryption, right? And to do this, we're going to use this big cryptee ruby. And this is pretty much easy to use. We just call the create method on the password class and passes a string. And this is the result for Bruce Wayne super secret password. And this is how our users array looks like now. But in order for this to work, we have to change our strategy. We have to perform an indication two steps now. This is like the device strategy. So we try to find the user for the email first. And if the user doesn't exist, we just fail right away. But if it does, we check whether the password is valid or not by calling this is password method from the big cryptee ruby gem. And if this returns true, we just call success as we did before. And if we test again, everything still works, except that for now we are using encrypted passwords. So this is pretty much all we have to see about strategies. You already know how we can implement a custom authentication logic. Now we're going to talk about another feature which is called scopes. And so we've been using scopes already. But to explain then, I want to talk to you about an e-commerce application. So imagine you have customers and they can search for items, add into a wish list and complete purchases. And we also have editors. They can manage those items. They can create promotions and pay me to refunds. And we have a requirement here that a person can be both a customer and an editor. So to solve this problem, we can have a user module with many roles. We create a customer role, an editor role, and this works. But we may end up with a big module that is responsible for too many things. So we have methods here that belong to customer and from editor. We could also create a single table inheritance here, but we may end up also with a table that has a lot of no columns. So for me, the ideal solution is to have different modules, different tables. And we know that this works with device, right? We can declare device in multiple modules. And it turns out that behind the scenes, device actually use a warning scope for each module. So what we pass to the device method is going to be a warning scope. And this is used in many places inside device. So for example, the signing path. You can see we have there slash editor slash sign in and slash customers slash sign in. And we also own the helper methods like current editor and current customer. And to warning, a scope is just a way to identify a user in the session. Okay, so if we sign in in this application as an editor, this is how our session looks like. We have warning.user.editor.key and the value one is the editor ID. And if we sign in in the same browser as a customer, this is how our session is going to look like. Now we have two keys in the session and they have different names and different values. So both users, they can coexist in the same session and share the same logic to sign in. And we can have different tables, different modules, and so on. And to use a scope, we don't have to configure anything. We just pass it as an option to the authenticate method. And if we don't pass any scopes, the default is going to be used. And we can pass scopes to multiple warning methods like authenticated when we ask if a user is authenticated or not. And when we are fetching the user from the session. And we can also pass a scope when we are logging the user out. And for the logout method, it is important to notice that if you don't pass any arguments, warning is going to log all the scopes out. So it's not going to just log the default scope out, it's going to log all of them. So we have to split to say which scope we want to log out. And, additionally, we can also add some configurations for its scope. So we can define which scope is going to be the default for our application. So we don't have to pass all the time in the warning methods. And we can also define the strategies we want to use for scope. And we can also say if your scope should be started on the session or not. So for the editor scope here, even when the user is signing, we're not going to start in the session. And this is pretty much all we have to see about scopes. Now we're going to see some callbacks. So Ward provides us some callbacks to the authentication events. And we're going to see which ones we have and how to use them. So first we have after set user. And this one is going to be called on three different situations. First, when we fetch the user from the session from the first time, by calling the user method. And when we authenticate the user. And when we set the user directly from the set user method. And as arguments, we receive the user, a warning instance, which is basically the same as the warning object in the environment hash. And also a hash of options. And those are options we can pass to the authenticate method. And since this can happen on multiple situations, we can pass some, a except or only options. And for the only option, we have some aliases. So we can declare a after authentication or after fetch callback. And we can also pass a scope. So this callback is used in many places inside device. I'm going to show some of them for you next. And so first, it is for the email confirmation feature. So when I use it right, when I use a sign in, we call this active for authentication method. And if the user's email isn't confirmed yet, this is going to return false. And when that's the case, we just log the user out. Another thing we do is for the remember me features. So right after user sign in, we just create a remember me cookie for this user. And we do this by calling this remember me method. There's a lot of logic there. I'm going to show this. But that's basically it. And we also have account tracking, right? So we have those columns, signing IP, signing count, and so on. So right after user sign in, we call this update track and fields. And we pass this request so we can extract an IP from it. Now, we also have an after field fetch callback. And this is going to be called when we try to find the user from the session, but the user isn't authenticated. And we have the same arguments as after set user. And we can also pass a scope. Next, we have our request. And this one is going to be called on each request right after wording is initialized. And as an argument, we receive the word in instance. Next, we have before failure. And this one is going to be called right after a field application when an authentication fails. And as arguments, we receive the environment hash. And also a hash of options. And this one can be useful if you need to change the environment hash in some way to provide more context to our failure application. And finally, we have before logout. And this one has the same arguments as we saw before. So user, a word in instance, and options. And we can also pass a scope. And device uses this for the remember me feature. So right after user logs out, we want to delete the remember me cooking. So we call this forget me method. And so those callbacks, they are stored in an array, which means they're going to be called in sequential order. So if you need to run a callback before the other ones, you can declare it by preficking its name with prepend underscore. So for example, here we have prepend before logout. And this works with any callbacks and the arguments and options stay the same. Okay. So a quick review of what we just seen today. We saw that wording is a rack based on middleware for authentication. It provides us some helpers to sign the user in, sign the user out, and to take care of session management and so on. And also, we saw how we can implement a custom authentication logic by using strategy. We saw how to handle authentication failures by using failure of the indications. And we also saw how we can have multiple type of users signed in the same session by using scopes. We saw some callbacks for the authentication events, how we can use them, and why they're useful. And between all that, we saw some device examples. Okay. So we have an idea of how this can be useful. And so device save us a lot of time by providing us features we have to write ourselves. Okay. And this is good because those abstractions, they help us move fast and watch more features for our clients. But if we ever get a chance, it is good to know how our tools work. And why is that? But first, because it's fun, right? And if that isn't enough, here are some benefits of knowing how a Jain works. And first, we can chase down bugs with more confidence. We can customize the behavior efficiently. So remember, in the beginning of the talk, I told you about the token-based authentication. Now we can imagine how we can implement this by creating a custom-oriented strategy, right? And this is, this can be pretty easy, way better than have to override the device sessions controller. And now that you know how Worden works, and you also have an idea of how device works, you can contribute to that, right? Because this is what Open Source is all about. So if you ever wanted to contribute to device, but you didn't know how to do it, come find me after the talk and I will help with everything you need. And I would love to accept your request from you. That is it. Thank you.
directly on our Gens features to build a custom authentication in Ruby web applications. And this interface for web service. And basically what it maybe Passenger or something else. And we can a method called and it's this method receives one going to have a simple authentication system comment there on the top. And to run a rack thing we have to do is we have to say how the method add on the Warden strategies class. And we our strategies message. And so I said before that saw how to handle authentication failures by beginning of the talk, I told you about the