We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Warden: the building block behind Devise

00:00

Formal Metadata

Title
Warden: the building block behind Devise
Title of Series
Number of Parts
88
Author
License
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language
Producer
Production Year2018
Production PlacePittsburgh

Content Metadata

Subject Area
Genre
Abstract
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.
38
Thumbnail
07:23
57
Thumbnail
05:41
60
64
BuildingBlock (periodic table)TwitterMultiplication signSlide ruleDiagramComputer animation
Software1 (number)Formal languageSoftware developerOpen sourceInformation technology consultingAuthenticationComputer animation
Fiber bundleTrigonometryAuthenticationGame controllerElectric generatorPoint (geometry)Different (Kate Ryan album)Projective planeMultiplication signLogicEntire functionComputer animation
Image registrationEmailPasswordData recoveryVideo trackingData managementAuthenticationLoginEmailProcess (computing)PasswordLecture/Conference
AuthenticationMechanism designWikiDisk read-and-write headGame theoryMiddlewareAuthenticationMechanism designWeb applicationComputer animation
Server (computing)Interface (computing)Server (computing)Web 2.0Interface (computing)Software frameworkWeb browserFile formatCartesian coordinate systemDependent and independent variablesComputer animation
EmailContent (media)Data typeSystem callSupremumServer (computing)MiddlewareHTTP cookieCartesian coordinate systemIntegrated development environmentDependent and independent variablesHash functionEmailServer (computing)Flash memorySocial classParameter (computer programming)InformationInstance (computer science)Revision controlSystem callComputer animation
Order (biology)MiddlewareMessage passingCASE <Informatik>Computer animation
System callContent (media)EmailData typePhysical systemCartesian coordinate systemConfiguration spaceSlide ruleAuthenticationComputer animation
Codierung <Programmierung>Heat transferRadio-frequency identificationData storage devicePasswordEmailData storage deviceMiddlewareStrategy gameObject (grammar)Order (biology)PasswordIntegrated development environmentHTTP cookieHash functionSet (mathematics)Computer animation
AuthenticationPasswordEmailStrategy gameLogicStrategy gamePasswordEmailSymbol tableParameter (computer programming)Message passingSign (mathematics)Computer animation
Integrated development environmentHash functionParameter (computer programming)BlogStrategy gameSet (mathematics)Default (computer science)Strategy gameDefault (computer science)Integrated development environmentHash function
Strategy gameAuthenticationPasswordHTTP cookieTexture mappingSet (mathematics)PasswordStrategy gameAuthenticationDatabaseMessage passingMobile appCovering spaceHTTP cookieObject (grammar)Order (biology)Token ringDefault (computer science)EmailComputer animation
EmailAuthenticationCartesian coordinate systemAuthenticationRegular graphRevision controlCombinational logicComputer animation
HTTP cookieData typeContent (media)Codierung <Programmierung>Heat transferEmailPasswordHTTP cookieWeb browserSet (mathematics)Cartesian coordinate systemAuthentication
Strategy gamePasswordEmailAuthenticationMessage passingHeat transferCodierung <Programmierung>Message passingWordObject (grammar)PasswordStrategy gameComputer animation
PasswordEmailAuthenticationStrategy gameResultantEmailMessage passingOrder (biology)Inheritance (object-oriented programming)Validity (statistics)PasswordAuthenticationComputer animation
HTTP cookiePasswordStrategy gameAuthenticationComputer animation
PurchasingText editorCartesian coordinate system
PurchasingData modelText editorEndliche ModelltheorieStrategy gameAuthenticationDefault (computer science)BlogLoginConfiguration spacePasswordSingle-precision floating-point formatSign (mathematics)Module (mathematics)Strategy gameLogicInheritance (object-oriented programming)Table (information)Default (computer science)Different (Kate Ryan album)AuthenticationOcean currentText editorConfiguration spaceMultiplication signCartesian coordinate systemKey (cryptography)Shared memoryComputer configurationWeb browserQuery languageComputer animation
AuthenticationGroup actionMathematicsComputer configurationMessage passingProxy serverHardware-in-the-loop simulation1 (number)Event horizonMultiplication signLogicSign (mathematics)Object (grammar)LoginIntegrated development environmentHash functionAuthenticationComputer configurationCartesian coordinate systemInstance (computer science)EmailContext awarenessParameter (computer programming)Computer animation
AuthenticationLogicStrategy gameEvent horizonAbstractionRadio-frequency identificationSource codeLogicMultiplicationType theoryConfidence intervalSoftware bugAuthenticationOpen sourceMultiplication signSign (mathematics)Client (computing)Data management
Coma BerenicesBlock (periodic table)Data typeXMLComputer animation
Transcript: English(auto-generated)
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 Tegon. But I think my mom is the only person that still calls me by my first name. Most people call me Tegon, which is how you can find me on GitHub. And on Twitter, I'm TegonL. These slides are going to be at speaker deck slash Tegon.
And this is also my first time in the West because I'm from Brazil. I live in Sao Paulo. And I work at Plata Pharma Tech. And this is a picture of our team. And if you don't know us, we are software development and agile 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 gene, right? And since authentication is a very common feature, it is good to have genes 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 genes, they can only get us to a certain point. 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 gene 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 such as controller. And when we have specific requirements like this one,
we can rely directly on Warden's 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 a Ruby gene, right? So Warden is a Raky middleware 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 Raky middleware is, it is hard to understand. So to make sure we're on the same page, I'm gonna show you now what is Raky. So Raky 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 name 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 warding 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 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 methods. 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 warding's middleware. And at this point, warding's going to say, hey, in order to continue, a user has to be authenticated. Well, let's say in this case it isn't. So we throw a 401, unauthorize it, and the request just halts. Okay, you can't pass. And the other middlewares down the stack
are never going to be called. So back to our Rack application example, you can see this, this isn't doing much right now. But the idea of this talk is, as we go through all these concepts, we're going to implement them here. And by the end of it, we're going to have a simple authentication system working with warding. 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 warding 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 warding middleware comes in, warding is going to use this object to start a user in the session.
And warding is also going to provide us a object inside the environment hash. And this is what we're going to use to interact with warding. 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 warding in a Rack application, we have to do some setup.
And first we have to say which middlewares we wanna use. And so here we are just using the session cookie middleware and the warding's middleware. And it's important to say that the order here matters. So we have to declare warding 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 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 a password is valid or not. And to this example, we are going to use email and password signing. And to declare a strategy, we call the method add on the wardingStrategies 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 are 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 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 used success and fail. We can also call halt, which just stops the request execution. Redirect 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 ignores 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 this strategy, we just call authenticate on the word and object. And if we don't pass any arguments, the default strategy is going to be used.
So for our 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 called in cascading order. Until one succeeds or fails, or none are found relevant. So in this last example here,
we're just going to authenticate the 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 warning 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 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 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 we 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, we're just going to call another rack application to handle it. And this is what we call failure applications. So there isn't much to see here. This is just a regular rack application. We are just returning 401 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 failed application gets called.
And next, we just grab the user for the session by calling this .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 is blue swine.
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's response. So we have there 401, unauthorized, and something went wrong. But if you go back to your strategy,
when we're calling fail, we're actually providing a message here. And this gets stored on the word and object under the message method. So we can use this in our failed 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 strategy's 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 bcrypti-rubygen. And this is pretty much easy to use. We just call the create method on the password class, and pass is a string.
And this is the result for blue swine super secret password. And this is how our user's array looks like now. But in order for this to work, we have to change our strategy. We have to perform authentication in 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's valid or not by calling this is password method from the bcrypti-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 modern 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 into a wishlist, and complete purchase. 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 you 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 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 sign-in 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 warn, 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 warnings 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 explicitly say which scope we want to log out. And officially, 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 want to use for a scope. And we can also say if a 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 going to 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 for 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 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 Devise. I'm going to show some of them for you next.
And so first, it is for the email confirmation feature. So 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 gonna show this, but that's basically it. And we also have account tracking, right? So we have those columns, sign in IP,
sign in count, and so on. So right after a user sign in, we call this update tracked fields, and we pass this request so we can extract an IP from it. Now, we also have an after failed 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 on requests. And this one is going to be called on each request right after warden is initialized. And as an argument, we receive the warden instance.
Next, we have before failure, and this one is going to be called right after a failed 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 failed application.
And finally, we have before logout. And this one has the same arguments as we saw before, so user, a warden instance, and options. And we can also pass a scope. And the device uses this for the remember me feature, so right after a user logs out,
we want to delete the remember me cookie. 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 warden. So if you need to run a callback before the other ones, you can declare it 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 just seen today. We saw that warden is a rack-based 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 authentication. 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 can have an idea of how this can be useful. And so, the device saves us a lot of time by providing us features we'd have through wider cells, okay? And this is good, because those abstractions,
they help us move fast, and watch more features through 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? But if that isn't enough, here are some benefits of knowing how a gem 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-wired strategy, right? And this can be pretty easy, way better than have to override
the device's sessions controller. And now that you know how warding works, and you also have an idea of how the 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 the device, but 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.