Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Protect GraphQL Properties With JWT In A Node.js Application

TwitterFacebookRedditLinkedInHacker News

So you started playing around with GraphQL and Node.js. Did you happen to get up to speed with my previous tutorial titled, Getting Started with GraphQL Development Using Node.js? Regardless on how you’ve jumped into GraphQL, you’re probably at a time where you need to figure out how to protect certain queries or pieces of data from the general public through some kind of permissions or roles.

When building a RESTful API, the common approach to endpoint protection is with JSON web tokens (JWT). In fact, I even wrote a previous tutorial on the subject, but how does that have relevance to GraphQL?

We’re going to take the common JWT approach and apply it towards protecting queries as well as particular pieces of data in a GraphQL API created with Node.js.

Let me start by saying that I’m still learning about GraphQL. Heck, I’m not even an expert with it comes to JSON web tokens (JWT). The things I demonstrate in this tutorial work pretty well, but I’m sure they can be accomplished one hundred different ways and possibly even better. That said, it should be a starting point for getting you where you need to be with API security.

Another thing to note is that we won’t be integrating a database in this tutorial. We’ll be using hard-coded mock data. If you’d like to see some database integration, check out my previous tutorial which integrates Couchbase NoSQL.

Creating a New Node.js Application with Express Framework and JWT Support

Before we get into the nitty-gritty of JWT and GraphQL, we should probably create a new project. While we could use an existing project, for simplicity it is probably best we start from scratch. Assuming that you have Node.js installed and configured, execute the following somewhere on your computer:

npm init -y

The above command will create a package.json file with some of the essentials. The next step is to install all of the project dependencies which include things like Express Framework and GraphQL. From the command line, execute the following:

npm install bcryptjs --save
npm install body-parser --save
npm install express --save
npm install express-graphql --save
npm install graphql --save
npm install jsonwebtoken --save

We could have installed all of the above with a single command, but I thought it would be easier to read if it was split up. Essentially we’ll be using Express Framework and a GraphQL middleware to handle our API. We’ll use bcrypt to hash our account passwords and we’ll be using a library for JWT. In my implementation, GraphQL will be completely separated from account creation and token generation. So rather than using GraphQL mutations, we’re going to use a POST request with Express. This means our application will not be 100% GraphQL powered.

The final step before we start developing, go ahead and create an app.js file within your project. The app.js file is where all of our code will end up.

Preparing the Application for GraphQL and Express Framework

To avoid any confusion, we’re going to add some boilerplate code to our application in preparation to the bulk of what we’re trying to accomplish. Open the app.js file and include the following code:

const Express = require("express");
const BodyParser = require("body-parser");
const JsonWebToken = require("jsonwebtoken");
const Bcrypt = require("bcryptjs");
const ExpressGraphQL = require("express-graphql");
const GraphQLObjectType = require("graphql").GraphQLObjectType;
const GraphQLID = require("graphql").GraphQLID;
const GraphQLString = require("graphql").GraphQLString;
const GraphQLSchema = require("graphql").GraphQLSchema;
const GraphQLList = require("graphql").GraphQLList;
const GraphQLInt = require("graphql").GraphQLInt;

var app = Express();

app.set("jwt-secret", "polyglotdeveloper");
app.use(BodyParser.json());

app.use("/graphql", ExpressGraphQL({
    schema: null,
    graphiql: true
}));

app.post("/login", (request, response) => { });

app.listen(3000, () => {
    console.log("Listening at :3000");
});

Notice in the above code that we’ve essentially just imported the libraries that we downloaded and initialized Express. As part of the initialization process we are defining a secret to be used when signing and verifying our tokens. In reality, it should not be hard coded and should probably be a little more complex than my example string.

We also did some preparation for our GraphQL endpoint which has GraphiQL enabled and no schema as of now. GraphiQL will allow us to have a UI for prototyping our schema. As previously mentioned, we will have a RESTful endpoint for signing in and obtaining a token. Since we don’t have a database for this example, we won’t worry about creating an account, but instead hard code one into the login endpoint when we design it.

Defining Login Logic for JWT Management

With the foundation in place, now we can focus our energy towards creating a JWT based on a valid user login. Within your app.js file, let’s add content to the RESTful endpoint that we had created.

app.post("/login", (request, response) => {
    var user = {
        username: "nraboy",
        password: "$2b$10$5dwsS5snIRlKu8ka5r7z0eoRyQVAsOtAZHkPJuSx.agOWjchXhSum"
    };
    if(request.body.username == user.username) {
        Bcrypt.compare(request.body.password, user.password, function(error, result) {
            if(error || !result) {
                return response.status(401).send({ "message": "Invalid username and password" });
            }
            var token = JsonWebToken.sign({ user: user.username }, app.get("jwt-secret"), { expiresIn: 3600 });
            response.send({"token": token});
        });
    } else {
        return response.status(401).send({ "message": "Invalid username and password" });
    }
});

In the above code we are creating a mock for our user account data. The password is just a bcrypt hash for test and nothing special. The endpoint expects, but does not validate for, a username and password in the request. If the hashed password matches the provided password, we sign some information using the secret and set an expiration in seconds for the token.

Intercepting JSON Web Tokens in the Client Request Headers

Now that we have a token, the plan is to pass it with every GraphQL query via an authorization request header. Rather than overcomplicating our GraphQL resolvers, we’re going to create a middleware for evaluating the header of every request.

Within the app.js file, include the following code:

app.use((request, response, next) => {
    var authHeader = request.headers["authorization"];
    if(authHeader) {
        var bearerToken = authHeader.split(" ");
        if(bearerToken.length == 2 && bearerToken[0].toLowerCase() == "bearer") {
            JsonWebToken.verify(bearerToken[1], app.get("jwt-secret"), function(error, decodedToken) {
                if(error) {
                    return response.status(401).send("Invalid authorization token");
                }
                request.decodedToken = decodedToken;
                next();
            });
        } else {
            next();
        }
    } else {
        next();
    }
});

I’m sure there is middleware available online, but it wasn’t too much of a hassle to create our own. What we’re saying is we want to look a the current request header. If an authorization header exists, split it and make sure it is a bearer token. If we found a bearer token, verify the JWT signature and store the decoded token so it can be accessed on the next stage of the request.

If no token is provided, fine, because we’re going to have unprotected endpoints. Being authorized is not a requirement to use our API, it is only a requirement for certain parts of it.

The decoded token might look something like this:

{ user: 'nraboy', iat: 1530148539, exp: 1530152139 }

Since we potentially have a decoded and valid JWT, we can start working on our GraphQL logic with protected pieces of data and queries.

Building a GraphQL Schema with Queries and Application Logic

Remember, we’re not using a database for this example. Before we can define our schema and start creating queries, we need to have some mock data in place to model after. Let’s use a blogs and users data example, kind of like a user profile store.

Within your app.js file, include the following simple data:

var accountsMock = [
    {
        id: 1,
        username: "nraboy",
        password: "$2b$10$5dwsS5snIRlKu8ka5r7z0eoRyQVAsOtAZHkPJuSx.agOWjchXhSum"
    },
    {
        id: 2,
        username: "mraboy",
        password: "$2b$10$gl3LguqEMDnt4MRr3oQNjuRZO3nWGRESIPuTfv0CC2h9/NB.kb4pe"
    },
];

var blogsMock = [
    {
        id: 1,
        author: "nraboy",
        title: "Sample Article",
        content: "This is a sample article written by Nic Raboy",
        pageviews: 1000
    }
];

If you wanted to be fancy, you could alter your login endpoint to look at accounts based on the mock array. However, for this example, the hard coded user account will get the job done. In the above data, the account data will be protected and the page view data will be protected. Only the signed in user should be able to access their account data and only a signed in user who is also the author of a particular blog post should be able to access its pageview metrics.

With the above mock data in mind, we can create GraphQL objects to model the data:

const BlogType = new GraphQLObjectType({
    name: "Blog",
    fields: {
        id: { type: GraphQLID },
        author: { type: GraphQLString },
        title: { type: GraphQLString },
        content: { type: GraphQLString },
        pageviews: {
            type: GraphQLInt,
            resolve: (root, args, context, info) => {
                return new Promise((resolve, reject) => {
                    if(!context.decodedToken || context.decodedToken.user != root.author) {
                        return reject("A valid authorization token is required");
                    }
                    resolve(root.pageviews);
                });
            }
        },
    }
});

const AccountType = new GraphQLObjectType({
    name: "Account",
    fields: {
        id: { type: GraphQLID },
        username: { type: GraphQLString },
        password: { type: GraphQLString }
    }
});

The AccountType object is simple, but the BlogType is a little more complex. The pageviews property has its own resolver function which obtains data to be returned. In the resolver function, we look to see if a decoded token was present and make sure it matches the author for any particular populated object. If there is not a match, we’ll return an error, otherwise we’ll return the original pageviews value.

Had this been hooked up to a database, there probably would have been a query in there to obtain the pageview information.

Now that we know that we can protect specific properties, let’s look at protecting entire queries:

const schema = new GraphQLSchema({
    query: new GraphQLObjectType({
        name: 'Query',
        fields: {
            account: {
                type: AccountType,
                resolve: (root, args, context, info) => {
                    return new Promise((resolve, reject) => {
                        if(!context.decodedToken) {
                            return reject("A valid authorization token is required");
                        }
                        for(var i = 0; i < accountsMock.length; i++) {
                            if(accountsMock[i].username == context.decodedToken.user) {
                                return resolve(accountsMock[i]);
                            }
                        }
                        resolve(null);
                    });
                }
            },
            blogs: {
                type: GraphQLList(BlogType),
                resolve: (root, args, context, info) => {
                    return blogsMock;
                }
            }
        }
    })
});

In the above code we have two different queries. The blogs query will return all blog entries, but the individual object rules that we previously defined will still be satisfied. The account query is a little different. To receive any data from the account query, a decoded token must be present as it is used when finding data. It is finding the data for the particular account that is signed in.

At the beginning we didn’t wire up our schema. Let’s fix that:

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

If you try to run the application, it should work fine, however GraphiQL doesn’t support custom headers to my knowledge. You can run your unprotected queries, but nothing that is protected with JWT. Instead, you can use Postman or cURL. If you wanted to give cURL a try, execute something like the following:

curl \
    -X POST \
    -H 'authorization: bearer <JWT_TOKEN_HERE>' \
    -H 'content-type: application/json' \
    --data '{ "query": "{account{username}}" }' \
    http://localhost:3000/graphql

The above command will return an error if no token exists in the header or an invalid token exists. If you wanted to try an unprotected query, try something like the following:

curl \
    -X POST \
    -H 'content-type: application/json' \
    --data '{ "query": "{blogs{title,content}}" }' \
    http://localhost:3000/graphql

The above command will get all blog content, but not the protected pageviews property. Adding the pageviews property to the query would require a JWT to exist in the request.

Conclusion

You just saw how to use JWT in your GraphQL powered application. There are always scenarios where you want completely public API data as well as API data that should only be accessed by authorized users. By making use of the context for GraphQL, we can work with header information and define policies.

Like I previously mentioned, there are plenty of strategies to solve this problem. I chose one from many and I’m sure it isn’t even the best there is. However, it works great and is a valid solution.

If you haven’t seen my previous GraphQL with Node.js tutorial or my previous JWT tutorial, I recommend checking them out because there will be further information with different explanations to help you.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.