Access Control and Data Segregation in multi-tenant GraphQL applications

Access Control and Data Segregation in multi-tenant GraphQL applications
Access Control and Data Segregation in multi-tenant GraphQL applications

If you have ever worked with GraphQL, you must know that ensuring proper data segregation and access control is implemented correctly is a nightmare, especially in multi-tenant environments.

Access control and data segregation are critical aspects of any multi-tenant application where multiple customers or tenants share the same application instance. In such environments, ensuring that each tenant's data is isolated and segregated from other tenants' data is crucial.

In GraphQL, this is even more problematic due to the graph nature of the API. Data can be accessed by multiple paths, sometimes that you're not even aware of, and all those paths must be secured the same!

Multiple Paths leading to the Same Stripe Token in GraphQL

This blog post will cover 3 Escape's features essential for ensuring tenant isolation and data segregation in GraphQL APIs.

#1 – Tenant isolation: each user has their own secret garden!

Tenant isolation is an important concept in multi-tenant applications. A multi-tenant application is a software application that serves multiple customers or tenants on the same application instance. These customers may have different data, different settings, and different permissions, and it's important to ensure that they are kept separate and isolated from each other.

In the context of GraphQL, tenant isolation means that different users or tenants of the application should not be able to access the same GraphQL object instance or scalar field value.

For example, let's say you have a GraphQL schema that exposes an User object which has a field called email. If tenant isolation is not properly implemented, two different users could potentially access the same User object instance and see each other's email addresses. This is obviously a serious breach of privacy and security.

This can be implemented at the GraphQL level by requiring authentication for all GraphQL requests and then implementing role-based access control or other authorization mechanisms to ensure that each user can only see the data they are allowed to see. In addition to these mechanisms, you can use GraphQL-specific techniques such as field-level resolvers to ensure that each tenant's data is isolated from other tenants. For example, you could implement a field-level resolver for the email field of the User object, which checks the user's authorization and only returns the email address if the user is authorized to see it.

Overall, tenant isolation is a critical aspect of building secure and scalable multi-tenant applications with GraphQL. By implementing tenant isolation at different levels, you can keep each user or tenant separate and only access the data they are authorized to see.

Escape offers advanced Tenant Isolation security tests to ensure at least 2 different users on your application should not access the same GraphQL object instance or the same scalar field value.

{
    "checks": {
        "access_control/tenant_isolation": { 
            "parameters":{ 
                "objects": ['**A list of objects that are private.**'], 
                "scalars": {'**Object**': ['**Field**']}, 
            },
        }
    }
}
Tenant Isolation configuration in Escapec

It takes 2 possible parameters:

  • objects: A list of private objects. 2 different users should not access the same instance of this object. Each object instance is identified by its ID.
  • scalars: A dictionary mentioning scalar fields inside their objects. 2 different users should not access the same value in those fields.

Here is the configuration that would test our example:

  • "scalars": {'User': ['email']}
Tenant Isolation result in Escape

#2 – Private fields: not everyone is an admin!

In GraphQL, queries and mutations are used to retrieve and manipulate data on the server. Queries are used to fetch data, while mutations are used to modify data.

However, sometimes certain queries and mutations may be used to fetch data or execute some actions that all users should not access. For example, an admin mutation may allow an admin user to create or delete users, which should not be accessible to regular users.

You can use authorization mechanisms such as authentication and permissions to restrict access to certain queries and mutations in GraphQL. Authentication involves verifying the identity of a user, usually by asking them to provide credentials such as a username and password. Once a user is authenticated, they can be granted certain permissions that determine what actions they can perform.

For example, in the case of an admin mutation, only users with admin privileges should be able to access it. You can use permissions to restrict access to this mutation so only users with admin privileges can execute it.

Here's an example of how this could be implemented in GraphQL:

type Mutation {
  createUser(name: String!, email: String!): User!
    @auth(requires: ADMIN)
}

enum Role {
  USER
  ADMIN
}

directive @auth(requires: Role!) on FIELD_DEFINITION
Roles in GraphQL

In this example, the createUser mutation is marked with a custom @auth directive, which requires the user to have a role in executing it. The Role enumeration defines the possible roles that a user can have.

When a user attempts to execute the createUser mutation, the GraphQL server checks their authentication status and their role. If they are not authenticated or do not have a ADMIN role, the mutation will be denied, and an error message will be returned.

In Escape, the Private Field security test is suited for the following use cases, among many others:

  • Your app contains Users with different roles. Some of those roles should not be able to execute specific queries or mutations or access certain fields in some objects.
  • Some queries are mutations should not be public.
{
    "checks": {
        "access_control/private_fields": { 
            "parameters":{ 
                {'**user**': {'**object**': ['**list of field the user should not access**']}}: , 
            },  
            "options":{ 
                "empty_values_are_positive": False, 
            },
        }
    }
}
Private Field configuration in Escape

Private field security test takes one parameter:

  • user : A dictionary {objectName:[filedName]} representing object fields that the user is not supposed to have access to it. public It is the default unauthenticated user.

and one option:

  • empty_values_are_positive: Consider that the server returning a null value on a specific field is a security issue.

In our previous example, let's say that Bob is a normal user (not ADMIN role). The following configuration would be adapted to test this use case:

{
    "checks": {
        "access_control/private_fields": { 
            "parameters": { 
                {'Bob': {'mutations': ['createUser']}}
            }
        }
    }
}
Private Field example configuration in Escape

Private Field result in the Escape Platform 

#3 – Private data: keep your information private

In GraphQL, it's common for some specific data to be considered private or sensitive and only accessible to authorized users.

Especially when you fixtured your application, you might already know that your user Bob should never access Alice's email: alice@example.com. Instead of creating multiple manual tests that are trying to get alice@example.com from Bob's account by all the paths that might lead to it, Escape does that automatically for you.

{
    "checks": {
        "access_control/private_data": { 
            "parameters":{ 
                "{'**user**': {'**fieldName**': ['**scalarValue**']}} 
            },  
            "skip": False,
        }
    }
}
Private Data configuration in Escape

Private data security test takes one parameter:

  • user: An array specifying some scalar values in specific fields that the user should never access. The field can be set to a pattern .* to look for the scalar value in all the GraphQL applications.

In our example, the following configuration would solve our problem:

  • {'Bob': {'.*': ['alice@example.com']}}
Private Data in the Escape Platform

#Bonus – Sensitive Data Leaks

In addition to the Private Data check test, Escape also checks for hundreds of pre-configured sensitive data leaks in your GraphQL application. In other words, the tool searches known data types that might hurt the user or your company if leaked.  These security leaks include private keys, credit card info, IBAN, social security numbers, etc...

When the scanner finishes scanning your GraphQL endpoint, you can see these data leaks in the Sensitive Data section of the tool, as seen below.

Sensitive Data results

Conclusion

Checking if access control is appropriately implemented in a multi-tenant GraphQL application was hard. Still, with this guide that helps you understand how to do that with Escape, you can instantly secure your graphQL endpoint. In case of any more questions regarding configuration and other Escape tool-related concerns, you can check the documentation page.

Food for thoughts

💡 Wanna learn about GraphQL testing? Read our blog article "How to test your GraphQL API?".