The objective of this tutorial is to understand how to implement authentication and authorization for GraphQL APIs using OIDC and OPA with Konnect.
The solution should solve for the Authentication and Authorization concerns at the gateway layer. First, users should be authenticated, and if authenticated, then the user’s fine-grain permissions should be evaluated to determine if user has permission to run the incoming graphql request (whether the request is nested, or using query variables).
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 query.
- Plus additional restrictions to demonstrate constant and query variable validation.
- Anyone with a valid JWT (kong and customers) should be able to see the frankfurter_currency_list.
The matrix of permissions is diagrammed below.
The prequisites for the tutorial:
- Konnect Plus Account - for use of OIDC and OPA Plugin
- Konnect Personal Access Token
- Docker and docker compose
- Insomnia Desktop Application
First, clone this repository:
https://github.com/Kong/kong-graphql-opa-authorization.git
Docker compose file will create three components: kong-net docker network, opa container, and keycloak container.
docker compose up
Login into Konnect at https://cloud.konghq.com/
Create the Konnect Runtime instance
In the default
runtime group create a new runtime instance --> Select Linux(Docker) --> Select Generate script
Copy the script and before running in the terminal we need to update add the --network=kong-net
to the command. An example below:
docker run -d \
--network=kong-net \ <-- add this arg to your command
-e "KONG_ROLE=data_plane" \
-e "KONG_DATABASE=off" \
-e "KONG_VITALS=off" \
...
-e "KONG_KONNECT_MODE=on" \
-p 8000:8000 \
-p 8443:8443 \
kong/kong-gateway:3.1.1.3
You should see the gateway successfully connected under Runtime Instances
menu.
You will create two clients in Keycloak, kong_id
and customer_id
that will be setup with client_credentials
OAuth flow.
Login to keycloak:
- url -
http:localhost:8080/admin
- username -
admin
- password -
admin
In the drop down menu on the left nav bar where it says master
:
- open the drop down menu
- select
Create Realm
- In the Create realm menu
- fill in Realm name
kong
- select
Create
Button
- fill in Realm name
Here we will will step through how to make the kong_id
client. This process needs to be repeated to create the customer_id
client as well.
-
In the left-panel the kong realm should be selected --> Navigate to clients --> select
Create client
--> fill inClient ID
withkong_id
-
Select
Next
at the bottom of the menu --> Toggle onclient authentication
, and for Authentication Flow toggleStandard Flow
andService account roles
--> PressSave
-
Navigate to Credentials Tab and copy the client secret. Save the client secret for later.
-
Repeat this process to create the
customer_id
client.
The graphql policy needs to be published to the OPA engine.
Execute the http request below to put the policy on the local opa server.
curl -XPUT http://localhost:8181/v1/policies/graphql --data-binary @opa/graphql.rego
The last setup task we will do is use to decK
to help expedite the setup of gateway. The kong.yaml
file has the configuration that we are going to sync up to Konnect.
The following configuration will be synced up:
- a gateway service pointing to a graphQL service
- route
- OIDC plugin - on the route - configure to the local keycloak docker instance
- OPA plugin - on the route - configure to the local OPA docker instance
To start, both the plugins will be disabled in order to validate the gateway service and route can reach the upstream GraphQL service, and then throughout the tutorial we will enable each plugin.
Execute the deck sync command below to push up the gateway configuration. If you used a different runtime group please correct for that.
deck sync --state kong.yaml --konnect-token <your-pat> --konnect-runtime-group-name default
Once the deck file has been synced in, take a moment to navigate konnect and review the configuration.
From Insomnia we will test that we call graphql query via the Konnect gateway running on our local workstation.
Open Insomnia and import the insomnia.yaml
Project, and open the Quickstart Collection
.
Execute the MyQuery-Konnect-kong_id
Request, and you should see a 200 status code with a response
{
"data": {
"frankfurter_currency_list": {
"AUD": "Australian Dollar",
"BGN": "Bulgarian Lev",
"BRL": "Brazilian Real",
"CAD": "Canadian Dollar",
"CHF": "Swiss Franc",
"CNY": "Chinese Renminbi Yuan",
"CZK": "Czech Koruna",
"DKK": "Danish Krone",
"EUR": "Euro",
"GBP": "British Pound",
"HKD": "Hong Kong Dollar",
"HUF": "Hungarian Forint",
"IDR": "Indonesian Rupiah",
"ILS": "Israeli New Sheqel",
"INR": "Indian Rupee",
"ISK": "Icelandic Króna",
"JPY": "Japanese Yen",
"KRW": "South Korean Won",
"MXN": "Mexican Peso",
"MYR": "Malaysian Ringgit",
"NOK": "Norwegian Krone",
"NZD": "New Zealand Dollar",
"PHP": "Philippine Peso",
"PLN": "Polish Złoty",
"RON": "Romanian Leu",
"SEK": "Swedish Krona",
"SGD": "Singapore Dollar",
"THB": "Thai Baht",
"TRY": "Turkish Lira",
"USD": "United States Dollar",
"ZAR": "South African Rand"
},
"frankfurter_convertedAmount": 9.73042
}
}
Now, we've validated the gateway setup is working. So the first activity will be to enable the OIDC, and validate the behavior.
In Konnect, navigate to the runtime manager --> to the graphql Route
--> Go to the Plugins Tab --> Toggle on the OIDC plugin.
When the OIDC plugin is enabled, the api call to the graphql endpoint should throw a 401 unauthorized
.
- From Insomnia, execute the
MyQuery-Konnect-kong_id
Request, and you should see a401 unauthorized
Because we have implemented the client_credentials
flow, we need to provide the client_id and client secret along with the graphql query.
-
In the
MyQuery-Konnect-kong_id
Request --> go to the Auth Tab on the request --> selectBasic Auth
--> add username -->kong_id
and password --> the corresponding client secret you copied from Keycloak. -
Execute the request again and you should get a 200 response.
-
Open the
MyQuery-Konnect-customer_id
Request and repeate the same process but with thecustomer_id
username and secret. You should see a 200 response.
With OIDC plugin correctly configured and validated, we can begin understanding how to rely on the OPA plugin for graphql authorization.
OPA language has GraphQL built-in functions that support parsing queries/mutations, verifying against a schema, and traversing the abstract syntax tree. We used the functions to build a graphql authorization strategy relying on parsing claims out of the JWT provided by the OIDC integration.
The core logic shows how to:
-
Parse query to AST and validate against the schema
-
Restricts the access controls based on claims. In this case kong_id users have more access than the customer_id users.
-
Parse input constants and validate those values
-
Parse input query variables and validate those values
Read through the entire graphql.rego file for the full list of helper functions to support the OPA rules, but the core logic is defined below.
#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
}
Navigate back to the route in Konnect and enable the OPA plugin.
Now that the OPA Plugin has been configured we can play with the graphql query request to validate the behavior.
Open the MyQuery-Konnect-kong_id
Request in Insomnia.
Test 1 - kong user query that are allowed
- Set the
amount
in the query variables to10.00
- Set
from
in the query toEUR
Outcome: You should see a 200 response with data
Test 2 - kong user queries that are NOT allowed
- Set the
amount
in the query variables to4.9
Outcome: You should see a 403 Forbidden
Status and a json response:
- Change amount in the query variables back to
10.00
- Set
from
in the query toMYR
Outcome: You should see a 403 Forbidden
Status
Nice - so we've tested through several type of possible access controls, using constants, and query variables, also parsing out a claim from the JWT.
Open the MyQuery-Konnect-customer_id
Request in Insomnia.
Test 3 - customer user queries that are allowed
- Execute the query in insomnia with no changes,
Outcome: You should see a 200
Status
Test 4 - customer user queries that are NOT allowed
- Copy the
frankfurter_convertedAmount(amount: $amount, from: "EUR", to: "CHF")
into the query and update query inputs (look at the screenshot below)
Outcome: You should again see a 403 Forbidden
status code.
Nice - so we've tested through several type of possible access controls, using constants, and query variables, also parsing out a claim from the JWT, all surrounding a GraphQL API!
- Tear down the konnect docker container:
docker kill <container-name>
Tear down the Keycloak and OPA docker containers:
docker compose down
And you're basically all cleaned up.
This tutorial covered how to provide Authentication and Authorization to a GraphQL API with Konnect and OPA.
We hope this was insightful and fun. If you want to reach us, ask questions, or any other asks for patterns please open up an issue on this repo!
From yours truly - The Kong Partner Engineering Team