Talks

Authorization in the GraphQL era

Authorization in the GraphQL era

by Nikolay Sverchkov

In the talk titled "Authorization in the GraphQL Era" by Nikolay Sverchkov, presented at RailsConf 2020 CE, the speaker addresses the challenges and methodologies surrounding access control in GraphQL applications, particularly within the Ruby on Rails environment. Sverchkov discusses the shift from traditional RESTful architectures to GraphQL and highlights the implications this shift has for user authorization.

Key points of the talk include:
- Differences Between REST and GraphQL: Sverchkov compares RESTful and GraphQL architectures, noting that GraphQL operates through a single controller for data fetching, which complicates traditional authorization approaches.
- Identification and Authorization: He explains how user identification is typically handled with frameworks like Devise and emphasizes that while this framework works well with RESTful APIs, care must be taken when integrating it with GraphQL to avoid unnecessary complexity.
- Access Control Layers: The speaker discusses the importance of implementing fine-grained access control in GraphQL, suggesting that roles alone are not sufficient. Using libraries such as CanCanCan or Pundit provides more robust solutions for implementing authorization.
- Use of Visibility and Policies: Visibility functions in GraphQL can manage field-level access control, while existing frameworks like Action Policy can facilitate authorization rules without bogging down the controller layer.
- Performance Considerations: Sverchkov touches on performance concerns, emphasizing the need for efficient data loading and caching, especially in GraphQL where users often request specific fields.
- Testing Authorization Logic: The necessity of integrating tests for authorization logic is discussed, indicating that tests need to ensure policies are correctly enforced without becoming overly complex.

The major takeaway from Sverchkov's presentation is that while GraphQL provides unique opportunities for flexible data queries, it also necessitates a reevaluation of how authorization is handled. The speaker encourages leveraging existing libraries that are familiar, while also proposing that there are many avenues for improvements and contributions within the GraphQL ecosystem. Overall, the talk serves as a guide for developers looking to navigate the nuances of implementing effective authorization in GraphQL applications.

00:00:09.209 Hello everyone. To start off, I want to thank those who donated to our unusual conference. I hope you can enjoy this talk while sitting on your couch. Who knows, maybe this forced constraint will become a stimulus for evolution, leading to new and interesting conference formats. Because video is a limitless source of opportunity, we can overcome restrictions and quickly return to the original planet of conferences. When I'm in Portland, OR, we can have conversations with famous people from the Ruby community. For example, we could call Aaron Patterson and ask her what she thinks about GraphQL characterization. Aaron is super friendly, and I'm 100% sure she will not refuse us. The possibilities of the online world are truly endless, and platforms like TikTok prove that people possess immense creativity.
00:00:30.189 However, this is one of our first attempts at an online format, and we didn't have much time to prepare and record the video. Therefore, I'm going to limit this introduction to a couple of opening slides with my face. So, let me introduce myself. My name is Nikolay Sverchkov, and I am a creative back-end engineer at Evil Martians. We are engaged in product development and help companies such as eBay and Font Awesome grow their products. We are fans of open source, and I think you have heard about our front-end libraries like After Effects, Prefixer, or various RubyGems like AnyCable and TestProf. We also started working on an open-source blockchain project, and now we are developing AstroGraph, which is a graphical interface for Stellar. For more information, you can visit our website, evilmartians.com.
00:01:09.099 As for me, I am from Tatarstan, a place in Russia that you may not have heard of. Typically, I might say the phrase 'come to me after the talk, and I'll give you cool stickers with Evil Martians in national costume from the place where I live', but alas, we have to wait a little for that. Okay, enough about me. Let's move on to the topic of the talk. It's customary for a speaker to briefly go over their vision of the topic.
00:01:42.299 I know that sometimes when people see GraphQL in the title, they think, 'Okay, the speaker will try to sell me on GraphQL and tell me how amazing it is,' but that is not why we are here today. I would like to stick to the basic concepts instead of going into a detailed introduction to the technology. Sometimes I doubt the benefits of GraphQL because I see a lot of hype around it, but only a few actual benefits. It's extremely difficult to design a good GraphQL API. I thought that the best way to share my experience with GraphQL authorization is to show a comparison of GraphQL with something everyone knows: REST authorization.
00:02:24.840 I'm going to provide specific examples to illustrate these points. To begin, let's remember what RESTful architecture is. On this slide, you can see the standard Rails application architecture. On the left side is the user, while on the right side is our data layer for interacting with it, using data from a simple Active Record model. In the middle, we find the controller for connecting the user and the data. This is the architecture that Ruby on Rails provides out of the box.
00:03:11.600 As I mentioned in last year's conference in St. Petersburg, I'm a huge fan of Rails and think it's a fantastic framework, but Rails is just a skeleton for your project. Almost always, we need more from our applications. For example, let's imagine that we have a multi-tenant application, and we need to control access to two specific resources. In this case, we would have to add another layer of access control to the RESTful API.
00:03:44.340 So, what is RESTful architecture? REST is a software architecture style that defines a set of constraints to create web services. Okay, but what is GraphQL? GraphQL is a query language for APIs and a runtime for executing those queries with your existing data. Comparing a language and an architecture style sounds like a terrible idea, but to clarify this comparison, we are going to introduce the next architecture: a GraphQL Rails application. In this slide, we see the user and their data as we did in previous slides, but with something new in the middle.
00:04:34.719 If you have never seen a GraphQL project before, you might be surprised that in this setup there is only one controller and one route. It might seem strange, but it's true. Typically, we have a controller folder, but here we only have one action, with code that resembles what we see in REST applications. The whole logic for fetching and manipulating data resides elsewhere. This draws a clear distinction between GraphQL and REST.
00:05:04.410 So what are the GraphQL internals? In general, we have one big class called GraphQL::Schema, which has one execute method. This method takes a query and parameters from the request and then executes the query against the schema. Inside the schema, we have two key components: types and mutations. Types are responsible for operations that retrieve data, while mutations implement create, update, and delete actions.
00:05:37.800 For example, we can request a user and his profile or a user, their posts, and comments. You will notice that all these actions are interconnected. This introduction sets the context for what's to come. So our main question is: how do we implement an access control layer for a GraphQL Rails application? It’s important to understand whether we can reuse approaches from standard applications or if we need to develop new solutions.
00:06:16.430 We're going to start with a basic concept of identification. Identification is the act of proving the identity of the user, or more simply, determining which user is currently working with the system. To implement identification, we typically use frameworks such as Devise, which provide many ready-made functionalities out of the box. The question is how we can use this framework within the GraphQL context.
00:07:03.400 For example, we can implement all of Devise's actions, such as sign in and sign out, using GraphQL. However, that is not necessary at all. You can leave the log in and log out actions outside of GraphQL and use the default Devise controller. The same idea applies to authorization execution. The biggest mistake would be to transfer a user's identity directly into GraphQL, using cookies or other identifiers, which would greatly complicate the application's code and make it harder to write tests in the future.
00:07:56.319 So far, our solutions are no different from the standard RESTful API. Now let's move on to a more interesting issue: ensuring user authorization. In typical systems, there are two types of actions: those that allow access to everyone, including guests, and those that are only allowed for authorized users. For instance, anyone can read posts, but only users who are logged in can update them.
00:08:44.260 In a standard Rails application, we solve this using a before_action callback in each controller case. This sets a specific list of actions that are forbidden for guests. However, this solution is not the most maintainable; it’s powerful and easy to use, but it lacks flexibility. But what about GraphQL? In our GraphQL version, we have only one controller. If we protect it with a before callback, it will prohibit all actions for unauthorized users, which is not what we want.
00:09:09.699 We need to find a solution to prohibit the execution of specific nodes in the graph for unauthorized users. With every slide and code snippet that I share here next, I'll show you how we can implement specific access control rules. In this presentation, I’m going to show you the gems available for handling authorization in GraphQL and how they can help.
00:09:44.290 So we have a gem, known as GraphQL, which is a Ruby gem designed to simplify the process of defining these access controls. Context refers to a specific variable that is passed along the entire query execution stack, which allows us to track some kind of state inside the graph. With the new knowledge, we can look for examples of fetching a user and their profile. To do this, we create a user type class where we define a profile field as a resolver method.
00:10:34.990 We also create a profile class to describe the fields that we can request. Everything is really easy and straightforward. Now, knowing that the context is available everywhere, we can simply add a check for the current user. However, this code can quickly become unmanageable since we would end up duplicating the logic for each resolver.
00:11:07.100 To solve this duplication issue, we can implement a feature called visibility. Visibility in GraphQL is a Ruby-specific feature that allows us to define which fields and operations are accessible based on user roles. Its implementation is straightforward; we only need to add one method to the type concerned. However, this leads to another problem: reusing this functionality across different types.
00:11:43.660 Let's move the visibility method to a separate helper or model and add it to the base type class. However, doing this globally affects all types and could break parts of our graph. So, we utilize type initializers to fine-tune and add login requirements for specific fields. This way, we can control access using a DSL feature of the GraphQL Ruby gem, allowing us to specify fields that should be hidden from guests.
00:12:26.620 Visibility is not the only way to implement authorization based on identity for specific fields. To understand why this is a strong solution, we need to discuss introspection. You can ask the GraphQL schema for information about which queries it supports. Tools like GraphiQL can build a whole graph based on a given entry point. This exposed information could potentially give attackers more opportunities to find vulnerabilities within your GraphQL implementation.
00:13:15.300 So, if you have a non-public API, my advice is to completely disable schema introspection in production. If you’re writing a public API for third-party services, you can utilize the visibility features discussed earlier, which automatically hide information about protected branches of the graph. This concludes our discussion on identification; now let's talk about authorization.
00:14:12.080 Authorization involves specifying access to resources: Can this user perform this action? Is the user allowed to execute this operation? One of the most common authorization restrictions is role-based. For instance, in an application, we might have administrators or managers, and we want to restrict access to certain resources.
00:14:49.520 The question is whether we should use a build approach as we discussed earlier. The short answer is no; that would be a weak decision in terms of application development. Authorization rules should not be limited to user roles alone. Almost always, you have several layers of checks that cannot be covered with just boolean flags.
00:15:35.950 There are libraries like CanCanCan that help manage this complexity and make our lives easier. We’d like to continue using these familiar tools, especially when migrating from a RESTful application to GraphQL, or in a hybrid architecture where we don’t want to rewrite the entire authorization layer.
00:16:12.060 Let's review how everything looks for a standard Rails application. We typically discuss relationships where one rule applies to one action. However, this can often lead to confusion about what the rule applies to. The crucial thing to remember is that these rules should be treated as rules for a resource, rather than for a controller action.
00:16:55.450 GraphQL has its unique complexities, notably that nodes of the graph can represent not just types (i.e., resources) but also fields. This allows for more flexible configurations of access to specific fields of these resources, which can be a challenge in standard Rails applications, sometimes resolved through duplicating endpoints for different roles.
00:17:35.299 So, returning to our original question, can we reuse existing authorization layers? In a sense, yes, but simultaneously, we will find cases where the existing solutions don't provide enough coverage. For example, determining how to pass the current user along with certain associations or how to ensure authorization happens correctly.
00:18:23.560 Usually, these checks are covered by helpers that come with authorization libraries, but the current problem is that these libraries focus primarily on RESTful applications, making them unsuitable for GraphQL. This presents a significant gap in our tools, highlighting a need for better solutions tailored to GraphQL.
00:19:10.870 For instance, when using libraries like CanCan or Pundit, we need to have integration that accommodates GraphQL context and helps establish a seamless authorization pipeline. The Action Policy library, written by my colleague, provides a dynamic alternative that adapts to both traditional Rails and GraphQL environments.
00:19:50.420 With Action Policy, you can add a flag to a specific field, and everything just works. It simplifies much of the complexity involved in authorization logic. This library includes many additional helpers that simplify the process of implementing authorization for the GraphQL Ruby ecosystem. Instead of going through all these helpers in detail, let's consider specific features that could help address common needs.
00:20:46.760 For example, data scoping is critical to ensure users only get access to the posts that they should retrieve. If a user is requesting their posts, they should see only their own posts, and if they're an administrator, they should be able to see all posts, including deleted ones, unless they are specifically configured otherwise.
00:21:24.260 This logic is easy to implement with just a few lines of code using policies. It is essential to maintain clarity in these setups, and developers should always remember to write policy tests to validate the correct permissions are in place.
00:22:09.620 Another feature worth discussing is how optimizing the front-end interaction with the backend can significantly improve user experience. Many developers mistakenly implement the logic of whether a user can see a button in two places: on the backend and the frontend, which can lead to inconsistency. Instead, maintaining a single source of truth from the backend and then communicating that back to the frontend is the best practice.
00:22:56.760 This way, we can perform checks simply and efficiently, making sure the frontend correctly reflects back-end authorization rules. With GraphQL's flexibility, this can often be done with just one line of code, standardizing the response and error messages when users attempt unauthorized actions.
00:23:44.830 When it comes to mutations, which may be expressed in nested structures, developers should remember that complexities increase, and they might compromise the architectural purity of GraphQL. Protecting all change actions necessitates ensuring they are governed by policy checks. Here again, the best practice would involve leveraging features from the Action Policy library.
00:24:17.230 Lastly, when conducting testing for authorization logic, an important consideration would be to separate the testing of policies from the testing of GraphQL integrations. Integration tests can slow down the process and introduce complexity, making them less optimal for the job. Instead, we must primarily focus on verifying that policies are executed correctly within the context of operations making relevant queries.
00:25:07.430 In summary, the relationship between GraphQL and authorization presents unique challenges. Even though the GraphQL-Ruby framework provides authorization features, it is often more beneficial to leverage familiar libraries we already know and enhance our workflows as we transition towards a GraphQL-centric implementation.
00:26:03.410 This consolidation will ease our migration and help maintain stable identification and authorization methods. However, we cannot ignore the fact that the GraphQL ecosystem is still maturing and additional solutions are necessary to fill existing gaps in GraphQL. There are many challenges ahead, including how to effectively authorize mutations or control access in federated schemas.
00:26:39.180 With that, I conclude this talk. Thank you all for being here today. I hope this information was useful. Please feel free to reach out to me with any questions or comments. Also, don’t forget to subscribe and check out the Evil Martians blogs. Thank you and goodbye!