 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. And on 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 West because I'm from Brazil. I live in Sao Paulo. And I work at Plataforma Tech. 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 Ruby Genes like simple form and device. So device is an authentication gen, 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 all 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'll need different requirements for authentication. So when the time comes, it is good to know how the gen 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 sessions controller. And when we have specific requirements like this one, we can rely directly on our dense 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, Warden is the one doing the job. So Warden is actually taking care of signing a user in or out and session management and so on. So what is this Warden thing? And when I hear the word, the first thing that comes to my head is this guy, right? The Warden 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 Warden is a rack-in-the-ware that provides a mechanism for authentication in Ruby web applications. And this is pretty much explaining what is Warden in a short way. But unless we know what a rack-in-the-ware is, it is hard to understand. So to make sure we're on the same page, I'm gonna show you now what is Rack. So Rack is a Ruby interface for web servers. 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 Rack unifies the request and response API, we can replace Puma here with Unicorn and 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 named call and 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 our response headers and the third one our 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. Any middleware looks like a Rack application, but here we are actually receiving a Rack application instance. And this allows us inside the method call 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, word 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 and authorize it and the request just 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 isn't doing much right now. But the idea of this talk is as we go through artist 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 warning. 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 warning 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 warning middleware comes in, warning is going to use this object to start a user in the session. And warning is also going to provide us a object inside the environment hash. And this is what we're going to use to interact with warning. 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 warning 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 warning middleware. And it's important to say that the order here matters, right? So we have to declare warning after the session cookie middleware. Another thing we have to do is we have to say how the user is going to be serialized 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's here is just an array. We have two users here, Bruce Wayne and James Gordon. 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 we check whether password is valid or not. And to this example, we are going to use a mail and password sign in. And to declare a strategy, we call the method add on the warning 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 email 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 may 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 we 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 cookie exists in the valid method. And on the authenticated 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 an email 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 this 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 didn't cover here, which is this failure app setting. So when an authentication fails, Warden's going to call another rack application to handle. And this is what we call failure applications. So there's as 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 applications response. So we have there for zero one and authorize it 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 failure application, right? We 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 bcrypt rubygen. 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's super circuit password. And this is how our users are really looks like now. But in order for this to work, we have to change our strategy. We have to perform authentication two steps now. This is like the device strategy. So we try to find a 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 bcrypt rubygen. 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 one feature which is called scopes. And so we've been using scopes already, but to explain them, I want to talk to you about an e-commerce application. So imagine you have customers and they can search for items, add them to a wish list and complete purchases. And we also have editors. They can manage those items. They can create promotions and payment 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, like we create a customer role, an editor role and this works. But we may end up with a big module that responsible for too many things. So we have methods here that belongs 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 customer slash sign in. And we also own the helper methods like current editor and query customer. And to warden, 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 warden.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 the scopes to multiple warden 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 we don't pass any arguments, warden 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 see which scope we wanna log out. And, additionally, we can also add some configurations for each 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 warden methods. And we can also define the strategies we wanna use for a 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 sign in, we're not going to start in the session. And this is pretty much all we have to see about scopes. Now we're gonna see some callbacks. So warden 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 warden instance, which is basically the same as the warden 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 accept 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 features. So when a user, right, when a user 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 not 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 signing, 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 fetch 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 warding is initialized. And as an argument, we receive the warding instance. Next, we have before failure. And this one is going to be called right after our failure 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 we 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 warding 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 preventing, by prefixing 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 warding is a rack-based and 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 authentications. 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'd 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? First because it's fun, right? And if that isn't enough, here are some benefits of knowing how a game 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 can be pretty easy, way better than have to override the device sessions controller. And now that you know how working 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 a request from you. That is it, thank you.