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!