Walkthrough of Using GraphQL Shield

Walkthrough of Using GraphQL Shield

Posted in

GraphQL Shield is a library that provides a simple way to add authorization to your GraphQL server. It uses the Apollo server and the graphql-shield middleware to provide a declarative way to define per-field or per-type access control rules for your GraphQL schema. You can use it to add authorization to your existing GraphQL server, or you can use it to build a new server from scratch.

Importance of Using GraphQL-Shield

GraphQL Shield is a great tool for protecting your GraphQL server from malicious queries. It allows you to whitelist the fields and arguments you want to expose and block all other requests. This ensures that your data is safe from unauthorized access and that your server can’t be overloaded with unwanted requests.

Another important benefit of using GraphQL Shield is that it can help improve the performance of your application. By declaratively specifying the data requirements of your application, you can optimize the underlying query execution to minimize the amount of data fetched and minimize round-trips to the server. This can result in a significant performance improvement for data-heavy applications.

GraphQLShield is also very easy to use and integrates well with existing GraphQL servers. It’s well-documented, and a community of users can help you if you face any problems. Overall, it’s a great tool for protecting your GraphQL API.

Create a GraphQL Server

To use GraphQL Shield in your project’s server, first, you need to create a new GraphQL instance with an Express framework and the GraphQL plugin.

Don’t miss our workshop with Apollo GraphQL: What If All Your Data Was Accessible in One Place

Step 1: Initializing the Project

Here we’re going to create a project directory and initiate a Node.js project. Once in that directory, we’ll set up a package.json file so that we have a place to stash all of our dependencies!

npm init

Step 2: Installing Dependencies

Next, we create an empty script file called server.js and install some dependencies.

npm install --save graphql
npm install express 
npm install --save express-graphql
npm install @graphql-tools/schema

Step 3: Creating Schema With Server

Now, let’s create a simple schema with some dummy data and resolvers that map to those dummy data entries.

const express = require('express');
const {graphqlHTTP } = require('express-graphql');
const {makeExecutableSchema} = require('@graphql-tools/schema');

// GraphQL Schema
const typeDefs = `
    type Query {
        me: User
        users: [User!]!
    }
    type Mutation {
        createUser(input: CreateUserInput): User
    }

    type User {
        id: ID!
        name: String!
        email: String!
        role: Role
    }

    input CreateUserInput {
        name: String!
        email: String!
    }

    enum Role {
        USER
        ADMIN
    }

`;

// Root resolver
const users = [
    {
        id: "1",
        name: "Vyom",
        role: "USER",
        email: "vyom@geekyhumans.com"
    },
    {
        id: "2",
        name: "Thor",
        role: "USER",
        email: "thor@graphql.com"
    },
    {
        id: "3",
        name: "Captain America",
        role: "USER",
        email: "captain@graphql.com"
    },

];

const resolvers = {
    Query: {
        users: () => users,
        me: (_, __, ctx) => users.find(({ id }) => id === ctx.headers["user=id"]),
    },
    Mutation: {
        createUser: (_, {input}) => ({
            id: "4",
            role: "USER",
            ...input,
        }),
    },
}

const schema = makeExecutableSchema({ typeDefs, resolvers });

const app = express();


// Create an express server and a GraphQL endpoint

app.use('/graphql', graphqlHTTP ({
    schema: schema,
    graphiql: {
        headerEditorEnabled: true,

    },
}),
);

app.listen(4000, () => {
    console.log('Express GraphQL Server Now Running On localhost:4000/graphql');
});

After successfully installing the Server, run the following command to check your server to see if it returns the correct output.

node server.js

Output:

Step 4: Adding graphql-shield to the Server

Before we start, you must install graphql-shield from npm. Run the following command in your terminal:

yarn add graphql-middleware
yarn add graphql-shield

Before we can design the schema for our project, we first need to apply middleware to our schema and pass in that GraphQL Shield library. After that, we will import an instance of Shield from the GraphQL Shield package.

Note: As we move forward with the Shield Library, other package imports will automatically be available.

const express = require("express");
const { graphqlHTTP } = require("express-graphql");
const { makeExecutableSchema } = require("@graphql-tools/schema");
const { applyMiddleware } = require("graphql-middleware");
const { shield, rule, and, inputRule } = require("graphql-shield");

Adding GraphQL Schema and Root resolver as before:

const typeDefs = `
    type Query {
        me: User
        users: [User!]!
    }
    type Mutation {
        createUser(input: CreateUserInput): User
    }
    type User {
        id: ID!
        name: String!
        email: String!
        role: Role
    }
    input CreateUserInput {
        name: String!
        email: String!
    }
    enum Role {
        USER
        ADMIN
    }
`;
// Root resolver
const users = [
    {
        id: "1",
        name: "neeraj",
        role: "USER",
        email: "neeraj@graphql.com"
    },
    {
        id: "2",
        name: "rahul",
        role: "USER",
        email: "rahul@graphql.com"
    },
    {
        id: "3",
        name: "rohan",
        role: "USER",
        email: "rohan@graphql.com"
    },
];
const resolvers = {
    Query: {
        users: () => users,
        me: (_, __, ctx) => users.find(({ id }) => id === ctx.headers["user=id"]),
    },
    Mutation: {
        createUser: (_, {input}) => ({
            id: "4",
            role: "USER",
            ...input,
        }),
    },
}

const schema = makeExecutableSchema({ typeDefs, resolvers });

Define the isAuthenticated rule, then define the signature of the rule, and then pass the User-Id as a header to the GraphQL server on request.

const isAuthenticated = rule()(async (parent, args, ctx, info) => {
  return !!ctx.headers["user-id"];
});

Now let’s create another rule that states if a user wants to run the query, they must be admin and authenticated.

const isAdmin = rule()(async (parent, args, ctx, info) => {
  const user = users.find(({ id }) => id === ctx.headers["user-id"]);

  return user && user.role === "ADMIN";
});

Here we are creating another rule called inputRule:

const isNotAlreadyRegistered = inputRule()((yup) =>
  yup.object({
    input: yup.object({
      name: yup.string().required(),
      email: yup
        .string()
        .email()
        .required()
        .notOneOf(
          users.map(({ email }) => email),
          "A user exists. Choose another."
        ),
    }),
  })
);

Now, we are declaring the permission we’ll need to invoke shield and pass to the constructor and object that will map to our query and root types.

const permissions = shield({
  Query: {
    // "*": deny,
    // "*": allow,
    users: and(isAuthenticated, isAdmin),
    me: isAuthenticated,
  },
  Mutation: {
    // "*": deny,
    createUser: isNotAlreadyRegistered,
  },
});

Then we’ll declare schema with permissions, and use applymiddleware to pass schema and permission.

const schemaWithPermissions = applyMiddleware(schema, permissions);

Now, we have to update our application and then update the graphqlhttp package to point to the new schema with permissions instead of the old schema.

const app = express();

Create an e=Express server and a GraphQL endpoint.

app.use(
  "/graphql",
  graphqlHTTP({
    schema: schemaWithPermissions,
    graphiql: {
      headerEditorEnabled: true,
    },
  })
);

app.listen(4000, () => {
  console.log(`Server listening on http://localhost:4000`);
});

Output:

Now, if you run the server by going to this link — https://localhost:4000/graphql — you will get an error because you are not authorized.

But if we give it permission bypassing the user-id in the request headers like below, we can get the info:

And you can also reverse-check the authenticated user.

Output:

Here we are checking the new input rule, and for that, we are creating a new user with the same email address. It returns an error, so the rule is working correctly.

Now let’s try it with a different email address:

Final Words

GraphQL Shield is an original library that provides a way to protect your GraphQL API from unauthenticated users. This is particularly handy for public APIs where you don’t necessarily want to hardcode a username and password. Instead, you can use role-based permissions to protect your API.

Keep in mind of a new development on the graphql-shield repository since we wrote this piece: “graphql-shield is currently unmaintained.”