RailsConf 2018

Warden: the building block behind Devise

Warden: the building block behind Devise

by Leonardo Tegon

The video titled "Warden: the building block behind Devise," presented by Leonardo Tegon at RailsConf 2018, discusses the foundational role of Warden in the popular Rails authentication gem, Devise.

Key Points Covered:

- Introduction to Speaker and Context: Leonardo Tegon introduces himself and shares that this is his first RailsConf and conference talk. He highlights his professional background in software development consultancy, particularly in open-source contributions like Devise and Simple Form.

- Overview of Devise: Devise simplifies the authentication process in web applications, requiring minimal setup to implement robust authentication features. The challenges arise when specific customization needs exceed the capabilities of Devise, necessitating a deeper understanding of its underlying components.

- Introduction to Warden: Warden is described as a Rack middleware that provides a flexible, customizable authentication mechanism for Ruby web applications. This section explains the significance of Rack in standardizing interactions between different web servers and frameworks.

- Warden Implementation: Tegon explains how to implement Warden in a Rack application, detailing the setup process, including middleware configuration and user session management. He discusses various strategies for authentication, underscoring that customized logic can be built around them based on project requirements.

- Strategies in Warden: The speaker explains authentication strategies within Warden, illustrating with examples how they validate user credentials (e.g., using plaintext passwords for demonstration purposes). Strategies can adapt based on specific authentication requirements such as token-based authentication.

- Scopes and Callbacks: Tegon elaborates on the concept of scopes, allowing for multiple user types to exist simultaneously. He also discusses callbacks that Warden provides for various authentication events, emphasizing their utility in managing user sessions.

- Importance of Understanding Warden: The talk concludes with a discussion about the benefits of understanding Warden when customizing Devise, fixing bugs, and enabling contributions to open-source projects.

Takeaways:

- Familiarity with Warden equips developers with the knowledge to enhance and customize their authentication systems effectively, paving the way for smoother integration with existing applications.

- Understanding the components of your tools — such as Warden's mechanisms in Devise — empowers developers to troubleshoot more confidently and leverage advanced features within their apps.

00:00:10.880 Hi everyone, thank you all for joining me here. I really appreciate it.
00:00:15.120 This is my first RailsConf and also my first conference talk ever, so I hope you enjoy this. My name is Leonardo Tegon, but most people call me Tegon, which is how you can find me on GitHub.
00:00:21.960 I don’t tweet much, but when I do, my handle is @t_bone_l. These slides are going to be available on Speaker Deck, so you can check them out later. This is also my first time in the West, as I'm from Brazil. I live in São Paulo and I work at Tata. If you don’t know us, we are a software development and agile consultancy. You may have heard of us from our open-source work, as we are the ones behind the Legacy language and we have created some Ruby gems like Simple Form and Devise.
00:00:43.200 Devise is an authentication gem, and since authentication is a very common feature in web applications, it's beneficial to have libraries that handle it for us, so we don't have to code it from scratch. All it takes to have Devise working today is running a couple of generators, and that’s it.
00:01:10.680 Well, here’s the thing: those generators can only get us to a certain point. Hopefully, if your project succeeds, you will likely need different requirements for authentication. So, when the time comes, it’s good to know how the gem we are using works, so we can create the customizations we need without much trouble.
00:01:30.450 For example, I was working on a project where we had token-based authentication for a JSON API. This is something Devise doesn’t support out of the box. We ended up overriding the entire Devise status controller. When we have specific requirements like this one, we can rely directly on Warden's features to build a custom authentication logic. Today, we will see how to do this, and hopefully this can be useful for you in the future.
00:02:06.060 Devise has a lot of features, including email confirmation, password recovery, account locking, tracking, and so on. However, when we are talking about authentication, Warden is the one doing the job. Warden takes care of signing a user in or out, session management, and much more.
00:02:21.849 So, what exactly is Warden? When I hear the word, the first thing that comes to my mind is the Warden of the North. But I’m not here to talk about Game of Thrones! Here, we are focused on a Ruby gem. Warden is a Rack middleware that provides a mechanism for authentication in Ruby web applications.
00:02:37.690 To understand what Warden is, it's essential to know what Rack is. Rack is the Ruby interface for web services, unifying the API for both web services and web frameworks in Ruby. When we start a request from a browser to our Rails application, it goes through a web server (like Puma), then down through the Rails application in a format that conforms to the Rack API. The same is true for the response. Because Rack unifies the request and response API, we can replace Puma with Unicorn or Passenger, and everything still works.
00:03:11.400 Now, let’s see how we can implement a Rack API in an application. It’s pretty easy to do this; we create a class that responds to a method call. This method receives one argument, and we have to return an array with three items: the first one is the response status, the second is the response headers, and the third is the response body. This is pretty much how you implement a Rack API.
00:03:34.510 In this context, the argument we receive is the request environment. It is a hash that contains all the information related to the current request. For instance, it includes the server protocol, request method, request path, and so on. As I mentioned before, Warden is a Rack middleware. Any middleware looks like a Rack application, but here we are actually receiving a Rack application instance which allows us to change their environment hash in some way.
00:04:02.590 As a middleware, they can stop the request execution. For example, when we start a request, we can pass through a login middleware. At this point, Warden will say: 'Hey, in order to continue, a user has to be authenticated.' If the user is not authenticated, we throw a 401 Unauthorized error. The other middleware down the stack will not be called.
00:04:27.950 Going back to our Rack application example, the idea here is to implement these concepts throughout the presentation. By the end of it, we will have a simple authentication system working with Warden. You can follow along and refer to the slides later. Just remember to save this file and name it 'config.ru' as a comment at the top is provided.
00:04:57.400 To run a Rack application, you can use the 'rackup' command. Once we send a request, it’s important to notice that Warden doesn’t handle session storage directly; it has to run after a session middleware. Let's say we pass through a session cookie middleware. This middleware is going to create an object in the environment hash called `warden.session`.
00:05:15.840 When Warden gets this middleware, it uses this session object. Warden provides methods for authenticating a user and checking if a user is authenticated by calling methods on this object. To use Warden in a Rack application, we have to first configure which middleware we want to use. Here, we are using the session cookie middleware and the Warden middleware. It's essential to note that the order matters; we have to declare Warden after the session cookie middleware.
00:05:48.890 Another thing we need to do is say how the user is going to be serialized into and from the session. In this example, we simply serialize the user ID. We keep the ID later to find this user. Here’s a basic example with two users: Bruce Wayne and James Gordon. Please note that I am using plaintext passwords here just to keep the example simple.
00:06:06.510 Now back to our setup. The 'strategy' setting is important to understand because a strategy is where we put the logic to authenticate a request. If we use token authentication, we might try to find a user based on a given token or, for example, an email and check whether the password is valid or not.
00:06:26.410 In this example, we’re going to use a plain password strategy. We define the strategy by calling the `add` method on the Warden strategies class and passing an argument to identify it. In our case, we call it 'password'. We define two methods: first, we have 'valid', which returns whether a strategy should run or not, and we also need to define an 'authenticate' method.
00:06:57.330 Here, we try to find the user based on the provided email and password. If we find the user, we call 'success' and pass the user as an argument. If it doesn’t match, we call 'fail' and provide an invalid email or password message. The failure method drops into the Warden object under the message method, which you can use in your failure application.
00:07:17.680 Now, let’s add some encryption using the BCrypt Ruby gem. It’s easy to use; we just call the `create` method on the password class and pass in a string. This shows how Bruce Wayne's supersecret password is stored. Now in our users array, we need to change our strategy to perform authentication in two steps.
00:07:40.230 First, we find a user for the email; if the user doesn’t exist, we fail right away. If the user exists, we check whether the password is valid by calling the `is_password?` method from the BCrypt gem. If it returns true, we call success as before, and if we test again, everything should still work, but we are now using encrypted passwords.
00:08:14.660 Moving on to scopes, we have already used scopes, but let's clarify with an example. Imagine an e-commerce application. Customers can search for items or complete purchases, while editors manage those items. We need a person to be both a customer and an editor.
00:08:27.770 To solve this, we can use different tables or modules for customers and editors. Device allows us to declare multiple modules, with each module using a separate Warden scope. What we pass to the device method will be considered a Warden scope, and it’s used in many places within Device.
00:08:45.700 As an example, if we sign in as an editor, our session will hold information specific to that role, while signing in as a customer gives us that session context. This allows both users to coexist in the same application, providing us with the functionality we need.
00:09:16.480 When using Warden, we don't have to configure anything special; we just pass the scope as an option to the authenticate method. If we don’t provide a scope, the default one is used. This also applies when we call methods like 'authenticated' to verify if a user is logged in or to fetch the user from session data.
00:09:41.710 For logout, if we don’t pass any arguments, Warden will log out all scopes. It’s important to specify which scope you want to log out if that’s your intention. Finally, we can add some configurations for scopes, setting which scope is treated as the default for the application, and we can define strategies used for a specific scope.
00:10:09.610 Warden supports callbacks for authentication events. For instance, we have 'after set user', which gets called during three situations: when we fetch the user from the session, authenticate the user, or directly set the user.
00:10:39.420 As arguments, we receive the Warden instance (similar to the Warden object in the environment hash) and a hash of options. There are various uses for these callbacks inside Device, like checking if a user’s email is confirmed or creating the 'remember me' cookie right after signing in.
00:11:10.760 We also have 'on request', which gets called on each request after Warden has initialized. The 'before failure' callback is useful for modifying the environment hash after a failed authentication attempt.
00:11:35.210 With 'before logout', we use the same arguments as before. This is particularly useful for the 'remember me' feature, as we need to delete the cookie after the user logs out. It's noteworthy that callbacks run sequentially. If a particular callback needs to run first, we can prefix its name with 'prepared_'. This prefixing will ensure that it executes before others.
00:12:04.040 To recap, we’ve seen that Warden is a Rack-based middleware for authentication. It provides helpers for signing users in, signing them out, and managing sessions seamlessly. We learned how to implement custom authentication logic using strategies and handle authentication failures gracefully.
00:12:25.510 We also saw how to use scopes to manage multiple user types within the same session. Finally, we explored callbacks provided by Warden for authentication events and how we can leverage them effectively.
00:12:51.150 Understanding how Warden and Device work brings numerous benefits. It not only helps solve bugs more confidently, but it also enables efficient customization. For instance, when defining token-based authentication, you can create a custom Warden strategy rather than overriding the entire Device setup.
00:13:16.650 If you’re interested in contributing to Devise, feel free to reach out to me after the talk. I’ll be happy to help you with everything you need.
00:13:47.010 Thank you!