graphql / graphql/graphql-spec

[RFC] Schema Diffing for Authorization

Open
#592 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
14.6k
Forks
1.2k
PR merge metrics
No merged PRs in 30d

Description

This RFC is an attempt to address the problem of implementing an ACL or Authorization layer that precedes hitting an API endpoint in an API Gateway like implementation, while also enabling to implement schema driven clients that can switch on/off functionalities based on available (authorized) data and actions exposed on the schema.

## Problem
* Implement ACL in GraphQL
* Although it is generally good to implement ACL in business logic layer further below the API layer, it makes it inconvenient for the clients to determine what is and is not allowed for a particular user.
* The only way for a client to determine if they have access to something or not is by hitting the end point and then processing the response.
* Implementing ACL in situations where the business layer is not under control of the API
* In cases when we relay information from a service which isn't directly in our control but we would like to impose restrictions on user access
* Flexibility of applying ACL across services that have an internal ACL and those that don't. This is similar to the previous point, but is more specific to cases where we have access to the business logic layer, but we might need to add restrictions on the fly where the infrastructure to do so does not exist.
* Disable endpoints from being visible to certain users.
* This doesn't necessarily have to be a security feature, but can be utilized to inform clients about accessible nodes through introspection
* Simplifying developer experience by separating business logic and authorization logic
* Rather than having all business logic calls to be preceded by Authorization operations, it would be better to separate the problems and give control of authorization to a different layer that can work independently

## Proposal
In the API/GraphQL layer of execution we perform the following operations:
1. Obtain an authorization mask from an auth provider for the user's context.
2. Modify the schema by applying the authorization mask.
1. Start with a blank schema.
2. For each node and parameter in the authorization mask, copy over the node from original schema if it exists.
3. Continue with Request Execution.
![Schema Generation](https://user-images.githubusercontent.com/10543706/60602606-73651c80-9df7-11e9-9d8a-d7644ee7cbf0.jpg)

## Advantages
1. Business logic and other services can implement or not their own authorization, as main authorization can be controlled by teams responsible for security.
1. This empowers the security team to implement best practices in a domain disjoint from developers and allows the work to be done parallely.
2. Forensics can be performed at the API layer, instead of having to drill down to potentially 3rd party APIs.
2. UIs can be implemented as slaves to the presented schema, modules that a particular user does not have access to can be removed from the UI by introspecting the schema.
![Driving Clients](https://user-images.githubusercontent.com/10543706/60602636-8546bf80-9df7-11e9-88c3-a6660e9fedca.jpg)
3. Execution time can be minimized by eliminating requests made to data points that will end up resulting in errors about unauthorized access.
4. Different clients can be implemented with access to only certain parts of the business without exposing the entire business graph to them.
5. Does not degrade the developer experience by forcing to turn off schema introspection for hiding the unauthorized nodes.
6. Eliminates the possibility of graph traversal from unauthorized nodes.
![Security Difference](https://user-images.githubusercontent.com/10543706/60602652-8bd53700-9df7-11e9-8ae2-9ea3ff48843c.jpg)

## Considerations

The proposal has at least the following considerations.

### Performance
This mostly depends on the implementation of GraphQL as well as the logic that does the masking, in a good implementation we would apply a mask and cache the schema associated with that mask and on subsequent requests simply fetch the schema essentially removing any performance degradation.

As a benefit, nodes that are not authorized will not be executed as the request executor will not invoke the requests since they will not appear on the schema. This would essentially improve the performance by minimizing execution compared to implementations in which the requests need to be executed until the business logic layer responds with errors regarding unauthorized access.

### Generation of Authorization Mask
While the authorization mask is generated we might disable certain nodes which would break the schema as we might have orphan edges on different nodes pointing to the node which has been masked out.

This can be addressed by providing mask generators with a schema validation check and subsequently masking out the orphan edges.

### Unexpected responses
As a client might develop an application thinking of the entire schema being available, while their users may have limited access and receive incomplete responses.

This can be addressed by checking for null responses on the client as is the standard and from the server responding with an unauthorized access error message if needed.

### Auditing malicious users vs poor clients
We will now be hitting unauthorized requests in 2 cases,
1. A malicious user tries to hit the nodes which have not been exposed in the presented schema
2. A client has hard coded requests that will try to obtain all nodes regardless of whether the accessing user is presented with all the relevant nodes in the schema or not

We can look at the problem generally as trying to distinguish a maliciously crafted request accessing deprecated nodes vs an old client accessing deprecated nodes. This problem needs to be addressed on a case-by-case basis in forensics stage where a certain amount of contextual data, not necessarily related to GraphQL's request itself will be key in differentiating between the two. As it stands, we do not have a solution to this problem.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.