Secure GraphQL APIs with JSON Web Token validation and Authorization rules in Azure API Management
Published Apr 11 2024 06:49 AM 1,443 Views
Microsoft

GraphQL offers a wide array of advantages, such as reduced over-fetching of data and understandable description of the data in your API and makes it easier to evolve APIs over time. However, it also brings along its own set of challenges, particularly in security. 

 

One of the security concerns with GraphQL is introspection, which allows clients to query the schema to discover available types, queries, and mutations. While this feature can be incredibly useful during development and debugging, it can also pose a risk if not properly secured, as it can potentially expose sensitive information about the API’s structure and endpoint to malicious actors. 

 

Another significant security consideration is granular authorization, which revolves around controlling access to specific fields and/or operations within a GraphQL schema. This level of fine-tuned control is a powerful feature but can be hard to implement properly. 

 

In this blog post, I will walk you through how Azure API Management can effectively solve these issues by utilizing JWT validation, enforcing authorization rules based on the token’s claims, and deactivating introspection through the GraphQL validation policy. 

 

API Import and Security Requirements 

For this example, I will be using community-built Rick and Morty GraphQL API, which allows clients to query data related to the TV show’s characters, locations, and episodes. 

 

To create this GraphQL API within the Azure API Management service:  

  • Open the Azure portal in your browser 
  • Select your existing Azure API Management service (or create a new one)  
  • Select the APIs blade 
  • Select + Add API 
  • Select GraphQL to create a new GraphQL API  
  • Fill in the form:  
    • Choose a Display name (Rick-and-Morty)  
    • The name field will auto-fill with a suitable name  
    • Select Pass-through GraphQL 
    • Paste a GraphQL API endpoint (https://rickandmortyapi.com/graphql) 
    • Choose an API URL suffix (rickandmortyapi)  
  • Select Create to create the API 

akamenev_0-1712752172956.png

 

 

For this API I want to have the following security controls enabled: 

  • (1) Only clients with valid JWT can access the API 
  • (2) Introspection is disabled 
  • (3) Only clients with Admin claim can get the time at which the character was created in the database (created field in the character schema) 
  • (4) Limit the size of the request 
  • (5) Limit the depth of a query 

I will reference these security controls by a number in parenthesis as we add more configurations to the API.

 

Adding Policies 

Let’s start with adding JWT token validation: 

  • Create a named value to store token signing key 
    • Navigate to the Named values blade 
    • Select +Add 
    • Fill in the form: 
      • Choose a Name (jwt-signing-key) 
      • Choose a Display name (jwt-signing-key) 
      • Choose a Type (Secret) 
      • Paste your key in the Value form (I use ‘123412341234123412341234’ as a key which is a rather weak secret but sufficient for demo purposes) 
    • Add a <validate-jwt> policy to the API 
      • Navigate to the APIs blade 
      • Select your API (Rick-and-Morty) 
      • Navigate to the API Policies tab 
      • Select </> In the Inbound Processing section 
      • Add the following policy to the inbound section: 

 

<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized" output-token-variable-name="jwt-token"> 
    <issuer-signing-keys> 
        <key>{{jwt-signing-key}}</key> 
    </issuer-signing-keys> 
</validate-jwt> 

 

 

This policy validates the JWT present in the “Authorization” header of incoming requests. It checks the signature of the JWT using the provided signing key (jwt-signing-key), which we specified in the first step. If the JWT is invalid or missing, it returns a 401 Unauthorized response with the error message “Unauthorized”. The validated JWT is stored in the `jwt-token` variable for later use. (1) 

Next, we will add GraphQL validation policy to disable introspection and enforce authorization for the selected field. 

 

Add the following policy right after the JWT validation policy : 

 

<validate-graphql-request error-variable-name="validation-error" max-size="102400" max-depth="4"> 
    <authorize> 
        <rule path="/__*" action="reject" /> 
        <rule path="/Character/created" action="@(((Jwt)context.Variables["jwt-token"]).Claims["role"].Contains("admin") ? "allow" : "reject")" /> 
    </authorize> 
</validate-graphql-request> 

 

 

This policy is specific to GraphQL requests, and it performs a variety of query validations: 

  • `error-variable-name`: If validation fails, any errors encountered during the validation will be stored in the `validation-error` variable. 
  • `max-size`: This sets a maximum size limit (in bytes) for GraphQL requests. Requests exceeding 102,400 bytes will be denied. (4) 
  • `max-depth`: It sets a maximum depth for the GraphQL query. GraphQL queries can have nested structures, and this attribute limits the depth of nesting to 4 levels. (5) 
  • `<authorize>`: This section contains authorization rules for GraphQL requests. It determines whether a GraphQL query is allowed to proceed or should be rejected based on the path of the query and the user’s role obtained from the JWT: 
  • The first rule path rejects any GraphQL queries with a path starting with “/__*”. Paths like “/__schema” are typically used for introspection and schema-related queries. (2) 
  • The second rule path checks if the user has “admin” role in their JWT claims. If they do, it allows GraphQL queries with a path of “Character/created”, otherwise, it rejects them. (3) 

Resulting policy should look the following: 

akamenev_1-1712752206737.png

 

 

Testing the API 

After we added policies to our API, we can test that the security rules work correctly: 

  • Navigate to the Test tab 
  • Choose a character query, add fields to the query and pass in the id (1) 
  • Select Send at the bottom of the screen 

 

akamenev_2-1712752560452.png

 

 

We did not pass a valid JWT and got a 401 Unauthorized response back as expected. 

To test the JWT validation and authorization I will create two tokens using jwt.io with roles ‘user’ and ‘admin’ with a signing key used in the previous steps. 

akamenev_3-1712752560454.png

 

 

Note: for the test purposes you also need to add an `exp` field with a future date in a Unix Timestamp format 

 

To test the API with JWT, navigate to the Test tab and add `Authorization` header with a generated token. 

akamenev_4-1712752560455.png

 

 

Request without restricted fields and using JWT containing `user` claim. 

 

akamenev_5-1712752560456.png

 

 

Request with a `created` field and using JWT containing `user` claim. 

 

akamenev_6-1712752560456.png

 

 

Request with a `created` field and using JWT containing `admin` claim. 

 

By following these simple steps and incorporating a few lines of policies, the security of GraphQL APIs can be significantly enhanced when using Azure API Management. 

Co-Authors
Version history
Last update:
‎Apr 11 2024 06:48 AM
Updated by: