RailsConf 2018

Access Denied: the missing guide to authorization in Rails

Access Denied: the missing guide to authorization in Rails

by Vladimir Dementyev

In the talk 'Access Denied: the missing guide to authorization in Rails' by Vladimir Dementyev at RailsConf 2018, the speaker delves into authorization mechanisms within Ruby on Rails applications, emphasizing the absence of built-in tools for this essential function. The session is divided into a theoretical overview followed by practical implementations of authorization. Key points include:

  • Definition of Authorization: Authorization is distinct from authentication; it determines whether a user has the permissions to perform a specific action.
  • Common Confusions: The speaker clarifies the often-confused terms, explaining that authentication answers "Who are you?" while authorization answers "Are you allowed to do that?"
  • Four Lines of Defense: Effective authorization typically comprises (1) a physical model for managing access, and (2) an authorization layer for verifying permissions during user interactions with applications.
  • Authorization Models: The talk reviews various models, including:
    • Discretionary Access Control
    • Mandatory Access Control
    • Role-Based Access Control
    • Attribute-Based Access Control
      Each model has its own strengths and weaknesses, impacting how permissions are managed.
  • Tools for Authorization: The speaker compares popular authorization gems like CanCan and Pundit, discussing their pros and cons while highlighting their usual use cases in Rails applications.
  • Action Policy: Dementyev introduces a new gem, Action Policy, designed to fill gaps left by existing tools, emphasizing simplicity, readability, and reduced duplication of code in policy definitions.
  • Performance Considerations: The importance of caching in authorization checks is highlighted, with techniques demonstrated to optimize performance and ensure smooth application functionality.
  • Testing Authorization Logic: The necessity of thorough coverage for authorization tests is discussed, stressing that proper testing practices are crucial to prevent unauthorized access.
  • Development of Action Policy Gem: The speaker shares insights into the creation of the Action Policy gem aimed at enhancing the existing Rails authorization frameworks while addressing common pitfalls in authorization logic.

In conclusion, the talk presents a comprehensive approach to implementing effective authorization in Rails, encouraging developers to adopt structured methods and the new Action Policy gem for more efficient and manageable authorization practices.

00:00:10 Hi everyone, welcome to the 'Access Denied' talk. This presentation is going to be about authorization in Rails.
00:00:17 The first part will be a little bit theoretical, while the second part will be much more practical. So if you find the first part boring, please wait until the end.
00:00:27 Let me introduce myself. My name is Vladimir, but you can call me 'Vlog' for short, as it’s much simpler. Some people also know me as 'Belkin' if you prefer GitHub handles.
00:00:39 I came from Moscow to New York City and had a long drive yesterday to Pittsburgh. Sorry for the mess; I brought some snow with me.
00:00:54 I work for a company called Código Martians. A few words about Código Martians: we do a lot of product development for large corporates and small startups across a variety of industries.
00:01:09 We also engage heavily in open-source development, and I am happy to share some of our popular tools for frontend and backend development. Feel free to ask me about any of them.
00:01:22 Of course, we write about everything related to this on our blog. I guess most of you are familiar with it. For example, our post about Rails 5 was pretty popular and has landed in Pittsburgh.
00:01:36 If you have any questions regarding product development, open-source, conferences, or blog posting, feel free to reach out to me during the conference. Enough advertising—let’s talk about the talk itself.
00:01:55 As I promised, the first part will be theoretical, and we will discuss some naming conventions. Let's start with the definition.
00:02:12 This talk is about authorization, right? So, what is it? According to the Cambridge Dictionary, it refers to authority as a source of knowledge.
00:02:19 It is an act of giving someone official permission to do something. There are two main components here: we have someone, and we either allow them to do something or not.
00:02:32 The first problem with authorization is that it is often confused with other types of 'ations.
00:02:39 The first one is authentication. Many people don't know the difference between authentication and authorization.
00:02:50 I am sure that everyone knows the distinction, but for verification's sake, let's check it out.
00:03:00 Authentication answers the question: Who are you? Is it really you? There are actually two questions I combined here: identification and authentication, but for now, let's not distinguish them.
00:03:19 The simpler question is authentication, while authorization answers the question: Am I allowed to do this?
00:03:32 So when we talk about authorization, we are discussing relationships and permissions from the code's perspective in our Rails application.
00:03:40 In Rails, authentication happens like this: we initialize the context of our request execution, usually the current user. In 90% of applications, this is the case.
00:03:54 Authorization happens when we operate on this context and check various properties, whether explicitly or not, from the RubyGems perspective.
00:04:07 There are not so many gems for authentication, of course. Everyone knows at least a few of them, but there are many more for authorization.
00:04:20 I don't know why it is that way, but I know that most of you are familiar with CanCanCan and Pundit, the popular choices.
00:04:30 So, to address typical stack questions, people often ask: 'Hey, I am using Devise, what's wrong with it?' It’s usually okay, and I think everyone here now knows the difference.
00:04:48 There are other types of things that could be confused with authorization, such as system constraints.
00:05:01 System constraints refer to environmental conditions that do not pertain to a particular user or resource.
00:05:20 They usually deal with account limits and sales applications, or subscriptions. This differs in the type of response code returned to your user.
00:05:36 So, to sum up, we have four lines of defense in applications. Not every application has all four, but this is a typical diagram of trying to get to data.
00:05:53 The first part is a physician model for granting and revoking access from a business logic point of view. That's where we define permissions.
00:06:11 The second part, which I call the authorization layer, is how we check these abilities and integrate this model with your entry point to your data.
00:06:30 Typically, this is found in Rails applications' controllers. However, we will discuss other use cases later.
00:06:47 Now, let’s talk about the most boring part of the talk: the models used in computer science to implement access control. However, I don’t want to skip it, as it provides essential information.
00:07:11 There are several popular models to build access control business logic in your application.
00:07:24 They all look somewhat similar. The first one, the oldest one, is called Discretionary Access Control (DAC). It's quite simple.
00:07:38 We have users, resources, and we want to grant access. So, we create an intermediate model called 'permission,' which is just a record in the database.
00:07:52 This record describes which activity each user is allowed to perform on which resource.
00:08:06 Authorization itself is simply checking whether these records exist or not in the database. If they exist, the user is allowed to act; if not, they are denied.
00:08:20 The problem with this approach is that this kind of permission logic can lead to database explosion.
00:08:34 If you have numerous records and relationships, you have to manage all these permission records and clean them up, which can get complex.
00:08:48 This is why people have attempted to invent other models. The Mandatory Access Control (MAC) model, while similar, works differently.
00:09:00 It assumes that you have security levels for your resources, like 'top secret,' 'confidential,' or 'unclassified.' Each user possesses a security clearance from the same set.
00:09:20 You must ensure that the user's clearance is greater than or equal to the resource's level. If it is, the action is allowed; if not, it is denied.
00:09:45 It's a straightforward model. However, you cannot combine these clearances in multiple dimensions, leading to limitations.
00:10:09 Row-Based Access Control (RBAC) is a popular deviation from the discretionary model. It operates at an intermediate level of abstraction.
00:10:28 The RBAC model collects a set of permissions based on resources and actions, but it still does not operate on a single resource level.
00:10:45 As a solution, the Attribute-Based Access Control (ABAC) model has emerged as the top level of access control model evolution.
00:11:05 Most frameworks, both in Ruby and in enterprise languages like Java and .NET, operate on configuration, allowing construction of access control policies.
00:11:21 For example, policies can be written in a simple text format that compares resource properties against user properties.
00:11:37 There is a project by the National Institute of Standards and Technology, which describes action-based access control and invented an XML-based language to define these policies.
00:11:50 They initially built this system for tracking access to patient records in medical institutions, and now they're aiming to expand its use.
00:12:02 With that, let's move on to the second part. This talk is about the integration of authorization.
00:12:14 We will focus on verifying access and integrating this business logic into Rails without delving too deeply into role management or permissions.
00:12:30 First, I’ll show you a diagram I call 'Play Self Authorization in Rails.' This diagram serves as a reminder that it's an important part of our defense lines.
00:12:49 We primarily use authorization for resource protection within our business logic.
00:13:03 As a side effect, while working with Rails as a web framework, we also use this foundation layer to modify outputs, such as conditional checks on view templates.
00:13:20 If a user is allowed to destroy a resource, we display the delete link; if not, we simply don’t render that link.
00:13:36 We should also implement authorization on channels. I wonder if anyone here has used Action Cable? No? I understand why my talk on Action Cable was not accepted.
00:13:51 So we tend to implement permissions where necessary, just as a previous speaker discussed GraphQL.
00:14:07 We must apply appropriate authorization checks to our GraphQL queries, as there are no perfect solutions available yet.
00:14:22 Let's talk about two tools: CanCanCan and Pundit. How many of you are using CanCanCan? Almost half of you—that's a typical situation.
00:14:39 I conducted a survey a month ago with a couple of hundred responses, revealing that most people are evenly split between CanCanCan and Pundit.
00:14:59 The main reasons people choose CanCanCan are its simplicity, extensive documentation, active community, and ease of use.
00:15:16 On the other hand, some opt for Pundit, favoring its clear structure. Notably, CanCanCan is a configuration-based framework where everything is described in one file.
00:15:31 The policy class can quickly become complicated, which was my experience as I worked on a real-life application.
00:15:48 Each task led me to simplify the management of permissions, which is why I decided to focus on Pundit and similar approaches.
00:16:07 Let's look at the simplicity of Pundit for a moment.
00:16:10 Pundit operates with plain Ruby files for your policies. For each resource, you define a class with methods that correspond to controller actions, returning true or false.
00:16:26 However, its simplicity can lead to repetition in your code. For instance, the lack of namespace support can cause complications in larger applications.
00:16:41 People usually end up either hacking Pundit or trying to create their own solutions.
00:16:57 After working on several projects, I realized we had to build something new to alleviate the consistent customization work we were doing.
00:17:13 I decided to extract common features and wrap them into a new gem called 'Action Policy,' as Rails lacks an out-of-the-box solution for authorization.
00:17:32 Thinking about Action Cable, while it may be less popular than authorization, we need a solid solution for the latter.
00:17:47 In my view, the answer may emerge from Basecamp, but it seems that we haven't seen such a tool yet.
00:18:06 Action Policy is designed based on the concept of bounded contexts and introduces a new gem I aim to showcase.
00:18:18 The gem promotes usability, focusing solely on policy objects and isn’t burdened by Pundit's intricacies.
00:18:32 It emphasizes readability and should be easy to understand.
00:18:44 Let's discuss boilerplate code next. Action Policy aims to reduce the amount of code necessary to implement authorization.
00:19:02 Here is a curated example relating to an admin user role. An admin or super admin should ideally have broad permissions.
00:19:22 In case you manage complex authorization flows, it quickly becomes complicated due to deeply nested conditions.
00:19:37 However, Action Policy streamlines this with a simpler interface and pre-check functionality.
00:19:49 By applying pre-checks, you can simplify the application of permissions and reduce redundancy.
00:20:07 It helps you execute the necessary rules more efficiently without repetitive code.
00:20:23 Moreover, Action Policy uses conventions over configuration so you don’t have to define policies for every controller action.
00:20:39 We automatically infer policies based on predictable patterns. The framework recognizes when a policy should apply to a resource.
00:20:54 Let’s look at the default rules; instead of explicitly defining rules for every action, we can describe an overarching policy.
00:21:10 When a rule is not explicitly set, we can call a default policy for the action.
00:21:23 While useful, we need to be mindful of potential issues, like typos that could inadvertently apply rules incorrectly.
00:21:39 Let’s address performance next. When we deal with authorization, one critical question arises: what are we looking to measure?
00:21:54 Typically, we want to minimize the number of calls to the database. Often, methods invoking 'authorize' checks can become performance-heavy.
00:22:10 Using measures such as Active Support Notifications can help log requests for insight on how our authorization processes perform.
00:22:27 There are two main events that we can track: the act of calling 'authorize' and each rule that is applied during that process.
00:22:45 When testing various controllers, we found there were several calls for policy checks, and that can dramatically impact performance.
00:23:01 Deeply nested lists of resources checking access can quickly multiply the overhead of what you thought was a simple call.
00:23:18 For that reason, the goal should be to avoid calling the same rule multiple times in a single request.
00:23:34 We can implement caching to prevent unnecessary re-evaluations of the same rules during requests by using Rails cache mechanisms.
00:23:50 Using cached data can drastically reduce the load on the database and improve the speed at which requests are processed.
00:24:05 Let’s switch to testing our authorization logic. It's crucial to ensure we have sufficient coverage of the business logic in our tests.
00:24:24 You should focus on edge cases and make sure every entry point that utilizes authorization is thoroughly tested.
00:24:38 In many cases, developers test their authorization logic within request and controller specs, but with a lot of complexities.
00:24:50 Context is necessary when testing authorizations; every aspect should accurately reflect the potential edge cases.
00:25:09 Our application had thousands of tests, with roughly half dedicated solely to testing access control. This significantly increased our testing times.
00:25:25 A solution to this, using Action Policy, is to isolate policies and enhance testability.
00:25:42 Each authorization check can be tracked, which allows you to ensure that each policy is invoked at the appropriate time.
00:25:56 In this way, we can condense tests and ensure all relevant rules are covered alongside variations.
00:26:10 Thus, not only do we maintain high coverage, but we also enjoy the benefits of faster tests.
00:26:26 Moving on, I've mentioned we use scopes before.
00:26:39 Scopes can simplify filtering collections while maintaining a cohesive authorization system without over-complicating your policies.
00:26:57 Action Policy manages to retain namespaces and rules tied to them to bring clarity to your authorization checks.
00:27:10 This separation allows for greater flexibility as your application scales.
00:27:28 It’s also capable of integrating with internationalization, allowing messages to be clearer and localized for your users.
00:27:42 With features like failure handling, we provide meaningful feedback on what went wrong with the specific permissions check.
00:27:56 In cases where a check fails, we can specify personalized feedback that guides users on the next steps.
00:28:10 It’s important to ensure that users know why they don’t have access and what actions they can take to gain access.
00:28:23 So let’s summarize—it’s clear that Action Policy takes significant steps to improve the incredible world of authorization in Rails.
00:28:39 Reflection upon the previous models and tools reminds us why flexibility and clarity are crucial in effective implementation.
00:28:55 If you are looking for further readings or examples, feel free to explore GitHub, our documentation, or reach out to me for clarifications.
00:29:09 Thank you all for your attention, and I hope this talk enhances your understanding of authorization in Rails.
00:29:40 Thank you!