GraphQL Authorization at the API Gateway with Kong Konnect and OPA
In this blog we are going to learn about the technical challenges behind solving GraphQL authorization and how many organizations resolve it today. Then discuss how a Kong / OPA integration can help drive security standards in this space and bring some parity with REST API solutions. Last, we will end with a quick tutorial.
Introduction
It is no secret that many organizations are reluctant to adopt GraphQL due to the new nuances surrounding the protection of these APIs. It’s a fair statement because when it comes to GraphQL API security, it is not apples to apples with REST APIs.
If we were to do a quick venn diagram of the landscape it would look something similar to the following:
We can see that GraphQL and REST have a very similar set of vulnerability concerns. But when it comes to implementation we need to adjust almost every solution to account for the GraphQL API query language.
Authorization, in particular, is an interesting problem to tackle in the space (Does GraphQL Introduce New Security Risks?). Salt Labs found that authorization flaws are more likely to occur with GraphQL APIs than REST given the unique flexibility to query and mutate API calls. (API Threat Research).
Why is Authorization with GraphQL Hard to Tackle?
To really understand it is best to do a comparison against REST APIs. Let’s consider the disputes example application below.
In the REST world, endpoints are very statically defined with clearly defined inputs and outputs.
For an API client it takes 2 API calls to retrieve all necessary information to render on a mobile device or web page:
- First, they need to call GET /user
- Second, use the information to call for disputes GET /disputes
In contrast, with GraphQL, all the information can be retrieved with 1 query API call, but the query is a nested graph data structure. The user can call disputes, and with disputes, who knows maybe we can call up transactions the dispute is associated with.
“With great power comes great responsibility” - Ben Parker
For REST APIS, when it comes to implementing authorization logic it’s easy to think through, and very easy to implement from the Kong API Gateway - each endpoint gets authZ logic tailored to its specific use case.
On the flip side, the flexibility GraphQL provides is the same reason authorization needs are more difficult to tackle. The reality is that because the data is dynamic and in a graph-like structure, it's difficult to map all possible access control policy scenarios.
What data is being requested in the query? Because it needs to be traversed
_Is the incoming user allowed to query for the dispute id selected? _
Are the query variables acceptable?
Who is allowed to mutate a dispute, and very specifically what attributes ?
This problem compounds even further with GraphQL Federations.
How is GraphQL Authorization Solved For Today?
Due to the lack of security tooling in the GraphQL space, many development teams are pushed to build access control policies within the codebase itself (State of GraphQL - Pain Points).
There are few solutions in the GraphQL landscape that support authorization at the API Gateway layer. This is somewhat of an anti-pattern because gateways don’t just unify the entrypoint to internal APIs but also help standardize common tasks such as rate-limiting, caching, authentication and authorization.
While, having authorization at the gateway should not be the only solution in your GraphQL security arsenal, how can we elevate current standards such that GraphQL security has more parity with REST APIs?
In the next section we will discuss how Kong Konnect can drive a GraphQL authorization gateway solution and the benefits of doing it with Konnect.
_Just a note: we haven’t forgotten about our GraphQL Federation friends and do have a solution for you, but for now we will keep federations out of scope. _
How to Perform GraphQL Authorization at the API Gateway Layer in Kong Konnect
It is well known that Kong has a rich plugin ecosystem that enables you to apply features as needed. When it comes to access control capabilities, the three options are the ACL, OIDC and OPA plugins (Kong Plugin Hub).
The ACL and OIDC plugins don’t quite fulfill the needs for GraphQL. We need to be able to write more complex logic than both the plugins can handle - schema validation, parsing requests into graphql abstract syntax tree for easier traversal, query variable validation - all need to happen.
Open Policy Agent (OPA) has the capabilities to address all of these concerns.
What is OPA?
OPA is used “to decouple policy from the service's code so you can release, analyze, and review policies (which security and compliance teams love)” (Open Policy Agent Project).
It is gaining traction with enterprise organizations because it enables governance teams to drive towards Policy-Ops and Separation of Concerns paradigms. The benefits being seen are more transparency, more standards, and overall more security across the board.
To support the growing GraphQL community, OPA recently released built-in functions that provide all the tooling necessary to build access control policies: schema validation, GraphQL abstract syntax tree, query variable validation, and all of this can be combined with JWT token validation (Open Policy Agent - Graphql APIs).
Which finally brings us the reference architecture of Konnect + OPA integration.
Kong Konnect OPA Reference Architecture
The solution entails Konnect, OIDC for authentication, and OPA for access control.
We mentioned earlier that the OIDC plugin is insufficient for our access control use-case and that still stands. It is used in this reference architecture because it is still a recommended authentication mechanism and will issue JWTs to the users.
Let’s take a closer look at the architecture below.
Starting in the control plane layer (Konnect), within the Runtime Manager, you have a Gateway Service, Route, and the plugins (OIDC and OPA) configured. All this configuration is pushed down to the Kong Gateway in the dataplane layer.
Down in the dataplane layer, we have the Kong Gateway, Keycloak as the IdP, and the OPA policy engine. Keycloak will authenticate users and provision JWT tokens. The OPA engine will have a GraphQL authorization policy.
The experience for an API consumer will be:
- The GraphQL request hits the route on the gateway.
- The gateway redirects the user to the IdP to authenticate and issue a token.
- The JWT header and GraphQL request are passed into the OPA engine where the authorization policy evaluates if the user and query are authorized.
- If the OPA decision is allowed, then the request will be proxied to the upstream GraphQL service.
Why Bother with GraphQL Security with Konnect?
“Complexity kills. It sucks the life out of developers, it makes products difficult to plan, build and test, it introduces security challenges, and it causes end-user and administrator frustration.” — Ray Ozzie
The same still applies today, and it can kill you at the API gateway platform as well. Organizations need an API strategy that is prepared to evolve with their needs. To meet customer demands, organizations and the engineers driving these solutions have to continuously evolve. Today it’s REST, tomorrow is GraphQL or gRPC.
A federated API management platform that enables separation of concerns (policy and code) in a protocol agnostic way, is key to this strategy - because it simplifies the complexity.
Konnect is positioned to do exactly this. It is a federated API management platform, that is platform agnostic, protocol agnostic (Federated API Management - Balance Agility and Governance).
No more talking! Tutorial time.
Tutorial Time
This is a great short self-paced tutorial that you can run entirely from your local workstation.
To follow along and for more details you can clone down the GitHub Repo. Here we are going to do the abridged version.
The Scenario
We’re protecting the demo Frankfurter GraphQL API, an exchange rate API by StepZen.
In our scenario, we have 2 types of users, kong users and customers users set up in Keycloak.
- The kong users should have special privileges. These are the only users allowed to hit the frankfurter_convertedAmount type definition.
- Plus additional restrictions to demonstrate constant and query variable validation.
- Anyone with a valid JWT (kong and customers) should be able to query the _frankfurter_currency_list type definition.
The matrix of permissions is diagrammed below.
The next step is to understand the OPA policy built to handle this.
Understanding the GraphQL OPA Policy
Read through the entire graphql.rego file to understand the parsing, extracting variables, and constants, and all the helper functions. But the core logic is written below.
- Parse query to AST and validate against the schema
- Restricts the access controls based on claims
- Parse input constants and validate those values
- Parse input query variables and validate those values
#Parse Query to Abstract Syntax Tree and Validate against Schema
query_ast := graphql.parse_and_verify(input.request.http.parsed_body.query, schema)[1]
...extra logic...read the whole file....
# Allow kong_id client_id to convert from EUR
allowed_kong_query(q) {
is_kong_id
#constant value example
valueRaw := constant_string_arg(q, "from")
valueRaw == "EUR"
#look up var in variables example
amountVar := variable_arg(q, "amount")
amount := input.request.http.parsed_body.variables[amountVar]
amount > 5
}
#Allow all generic users to query list of of currencies
allowed_public_query(q) {
is_realm_access_default
}
...extra logic...read the whole file....
Testing out the Behavior Insomnia
Following along in the GitHub Repository, we’re moving on to Step 3 - Testing OPA Behavior, and will start testing the behavior of hitting the frankfurter API in Insomnia.
Testing Out Kong User Queries
First let's test out a query for the kong user that works.
The screenshot below shows an example of a successful query for the kong user.
We can also test out the more fine-grained access control examples written into the policy that showcase query variable and input validation.
For the same frankfurter_currentAmount type definition , if we change the “amount” to value less than 5, or the “from” input to another currency, such as “MYR”, we would have received a 403 Forbidden response.
_Testing Out Customer Queries _
According to the scenario, the customer is only allowed to hit the frankfurter_currency_list type definition, which is what the screenshot shows below.
If we include the frankfurter_convertAmount type definition in any form or fashion in the query, we would have seen a 403 Forbidden.
Wrapping Up
So we talked through the caveats of solving for GraphQL authorization, reviewed a reference architecture at the API gateway layer with Konnect and OPA, and stepped through the highlights of the self-paced tutorial.
The tutorial is publicly available on GitHub - GraphQL Authorization Pattern with Konnect, OPA and OIDC, and if you prefer to watch videos we have those as well.
Last, if you are looking to have a full enterprise grade solution with Konnect and OPA then Styra DAS may be for you. You can read more about that on this blog post Securing your Services and Applications with Styra Declarative Authorization Service (DAS) & Kong Gateway Enterprise.
Up Next - Extending Authorization Patterns to Support GraphQL Federations
With federations, an API consumer interacts with a supergraph, and the supergraph itself is a composition of several subgraphs each represented by a different subgraph microservice. Subgraphs are broken down into microservices managed by several teams, and the supergraph ownership is shared among the teams.
In the next phase of your GraphQL adventures, we want to step you through how to extend this same Konnect OPA framework to support GraphQL federations with Kong Mesh.