ShieldQL — a GraphQL security solution
Coauthors: Rodrigo Samour Calderon, Xin Jin Qiu, Siful Siddiki, Joie Zhang
The Problem with GraphQL
GraphQL is a popular query language for APIs, and it offers a lot of benefits for developers. GraphQL is strongly typed, it allows you to avoid under-fetching or over-fetching data, and it can make it easier to maintain large applications that require complex HTTP requests that may evolve over time.
Unfortunately, in providing the client side with more flexibility in requesting data, GraphQL is also more vulnerable to malicious queries, which may involve SQL/noSQL injections or infinitely nested queries/DOS attacks. In fact, according to The State of GraphQL 2022 report, security concerns remain to be one of the main pain points when using GraphQL.
ShieldQL as the solution
ShieldQL is a lightweight Express middleware library powered by OSLabs that addresses GraphQL security concerns. ShieldQL is a developer tool that facilitates the process of user authorization. It enables developers to authorize users based on their role (e.g.: admin, user, etc), and allows developers to easily limit the kinds of queries/mutations that users can make.
ShieldQL is partially inspired by GraphQLock. ShieldQL builds on GraphQLock’s work with the addition of query sanitization. ShieldQL denies queries that could be dangerously long or nested, and also denies queries that do not pass through a blocklist of common SQL/noSQL injection attack patterns. Therefore, ShieldQL reduces the potential for Denial-of-Service attacks. ShieldQL also contains functionality to deny queries that include common SQL/noSQL injection patterns (e.g.: ‘ OR 1=1).
How does ShieldQL work?
shieldql.json
Developers using ShieldQL determine available user permissions by creating a shieldql.json file in their root directory. The developer can list any number of user-types and specify each user-type’s permitted query and mutation fields. The code snippet below is an example shieldql.json file.
{
"admin": {
"query": ["."],
"mutation": ["."]
},
"user": {
"query": ["feed", "news"]
},
"job-applicant": {
"query": ["job-description"]
}
}
loginLink
loginLink is a middleware function that abstracts away the process of creating a JWT access token for a user based on their role.
Note that the developer will need to create their own middleware function to pass the role for a particular user to loginLink via res.locals.role.
Consider the following example:
const { loginLink } = require('shieldql');
app.post('/login',
populateResLocalsRole, //this middleware function will pass role via res.locals.role
loginLink,
(req, res) => {
return res.status(200).json(res.locals);
}
);
validateUser
validateUser will check if the user has a valid access token (created by loginLink), and if the user’s GraphQL query is permitted according to their role permissions (as defined in the developer’s shieldql.json file). If a user makes an unauthorized request, our middleware will trigger the developer’s global error handler and the request will not go through.
shieldqlConfig and sanitizeQuery
Use shieldqlConfig and sanitizeQuery to sanitize graphQL queries.
Invoke shieldqlConfig in the server file to configure how sanitizeQuery will restrict queries (more on this below). shieldqlConfig accepts the following arguments:
- strictShieldQL (default false),
- maxDepthShieldQL (default 10),
- and maxLengthShieldQL (default 2000)
Developers can pass their own arguments into shieldqlConfig to change query sanitization restrictions. shieldqlConfig will also create a JWT secret for each role listed in the shieldql.json file. shieldqlConfig will store all of this information in the .env file and the process.env object.
sanitizeQuery is a middleware function that denies malicious queries. By default, sanitizeQuery will deny queries nested more than 10 levels deep, and will deny queries longer than 2000 characters. Developers can customize the maxLength or maxDepth parameters when invoking shieldqlConfig in their server file (as discussed above). To check queries against a blocklist of common malicious patterns/fragments, developers can invoke shieldqlConfig by setting the first argument (strictShieldQL) to true. sanitizeQuery will then sanitize queries based on the developer’s preferences when invoked in the middleware chain.
(See example setup below):
const express = require('express');
const graphqlHttp = require('express-graphql');
const shieldql = require('shieldql');
const dotenv = require('dotenv');
dotenv.config();
// shieldqlConfig configures settings for sanitizeQuery
// if the first arg is true, sanitizeQuery will check queries against the blocklist
// second arg indicates maxDepth allowed
// third arg indicates maxLength allowed
shieldql.shieldqlConfig(true, 15, 5000);
const app = express();
app.post('/login',
populateResLocalsRole, //this middleware function will pass role via res.locals.role
shieldql.loginLink, // loginLink will use the role to create an access token
(req, res) => {
return res.status(200).json(res.locals);
}
);
app.post(
'/graphql',
shieldql.sanitizeQuery, // developers can invoke sanitizeQuery to sanitize queries based on the rules passed into shieldqlConfig
shieldql.validateUser, // validateUser checks if user is authorized to make the query based on their role and the permissions set in shieldql.json
graphqlHttp({
schema: graphQlSchema,
rootValue: graphQlResolvers,
graphiql: true,
})
);
How to get involved
We are very excited to release ShieldQL, as it addresses a major GraphQL painpoint. You can download our library by running:
npm i shieldql
We also welcome any contributions to the project. Please fork and note any issues, or create pull requests!
Currently, we are focusing on the following improvements:
- Adjusting logic in validateUser so it can reliably work when there are multiple root level queries or mutations in a single request
- Implementing refresh tokens to reduce the risks associated with access tokens being leaked
- Making the blocklist more robust
Other contribution ideas are noted in our GitHub ReadMe
For more information on ShieldQL: GitHub | Website
Coauthors & ShieldQL Team
Simran Kaur | GitHub | LinkedIn
Rodrigo Samour Calderon | GitHub | LinkedIn
Xin Jin Qiu | GitHub | LinkedIn