How to Auth: Secure a GraphQL API with Confidence

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] okay here we go i'd like to begin by thanking everyone for joining me for this talk today and before we jump in i'll share a bit of my background so again my name is mandy and i've been developing with graphql since 2017 and i started teaching other people how to use graphql shortly after that at a tech and design school i used to work at last year i decided i wanted to consolidate everything i had learned about building graphql apis over the years into a book and i called that book advanced graphql with apollo and react and i self-published it earlier this year now today i'm going to give you a quick rundown of how you can handle auth with your graphql api and my intention for this talk is to give you an overview of how we can handle authenticating and authorizing users with graphql apis and specifically i'm going to show you how this can be accomplished using various libraries including apollo server and we'll even talk a little bit at the end about how this works with apollo federation 2. now before we dive in it's important that we clarify what we mean when we talk about auth and there are two sides to this so first we have authentication which helps us identify that a user is who they say they are and once authenticated we want to make sure that users are only doing the things that they're allowed to do with our api given their assigned roles groups or permissions and this is what we refer to when we talk about authorization now a good place to start when deciding how we're going to handle auth with a graphql api would be to go to the graphql spec itself to see what it has to say about how we should best move forward and on the topic of auth the spec has this to say or perhaps more accurately it doesn't say anything specific about authentication or authorization at all so since we're choosing our own adventure here because there's no spec mandated mandated way to manage auth with a graphql api we can instead draw from a collection of tools and battle tested practices that have emerged over the years that will help guide us towards locking down our graphql api with confidence so let's start with authentication i find that a demo is often worth a thousand slides so i'm going to quickly set the scene here and then take you through some code examples so we can see some of these concepts in live action so first of all we're not going to lock down our entire graphql api endpoint we're typically going to want to authorize user access to our api on a per query or permutation basis and perhaps may even want to manage access more granularly on a per field level our second assumption is that we're going to use token based authentication and specifically we'll use a json web token or jot for short but you could use a similar approach with other kinds of tokens too finally we're going to use express with apollo server because it will simplify the jot handling and verification process with some ready-made middleware but it's not explicitly necessary to use express or any specific kind or any specific kind of node middleware to do this now one last thing before we jump into the demo if you're new to jots a typical json web token will look something like this and if you look closely at the string of characters you'll see that it consists of three parts that are delimited by periods first we have the header and the header will typically contain the token type and the algorithm used to sign the token in the middle we have the payload and here we'll find claims about a particular entity and the entity that we're dealing with in our case will be a user now the jot specification has some predefined registered claims and we can see examples of those here in the form of the issue.time the expiration time and the subject claim which for us refers to the user's unique id and we can also add some custom claims of our own to a jaw in the form of public or private claims and we're going to add a private claim to our dot which will name space using the url of our graphql api lastly we have the signature and this part of the token helps us verify that no information was changed during the token's transmission by hashing together the token header its payload and a secret and we can see an example of how that might be implemented here in pseudocode now as a final note even though a jot may look encrypted it's just base64 encoded to make it as compact as possible for transmission which means all the information inside can be just as easily decoded again so it's important not to put any secret information inside of the jot header or payload all right we're ready to jump into our demo so jumping into vs code you can see here that we have a very basic apollo server setup using express and in our type definitions we have a single object type defined here called user along with a corresponding query to get a single user by their id and in our resolvers we can see that we're pulling some mock data from a file called data js and in that file we have two different users and each of them have different roles and permissions assigned to them and of course for demonstration purposes only we have their passwords hard coded here in clear text now our first step is going to be to add some jot checking middleware to our server and for that we can use a package called expressjock which i have already installed so if we go ahead and run our first step here i'll highlight the code so we can see what changed so for this step we can see that we've now imported the middleware and we call that expressjot function and pass in a secret string to sign the draw and again for demonstration purposes only we're adding the sensitive value directly into the file but typically we would store something like this in an environment variable we also specify the signing algorithm to be hs256 which is a symmetric signing algorithm meaning that we'll need to use this secret both when we create a user's jot and when we verify it and lastly we set the credentials required option to false so express won't throw an error if a jot hasn't been included which would be the case when a user initially logs in or potentially when graphql playground pulls for schema updates and if you're wondering what kind of magic is happening under the hood here the middleware we just added to express will get the token from the authorization header of an incoming request decode it and then add it to the request object as a user property okay we're ready for step two now so i'll go ahead and add that code and if we scroll down i'll highlight the code changes and we can see that we're now taking advantage of apollo servers context option so once the middleware has added the decoded token to the request object we can use apollo servers context to pass this data down the graph to the resolvers and it's a common practice to add decoded tokens to apollo servers context because this object is conveniently available in every resolver and it's recreated with every request so we won't have to worry about tokens going stale so here we check for the user object in the request and add it to the apollo server context if it exists otherwise we just set user to null because we don't want to error out if a token isn't available so now that we can handle an incoming token we need a way to create one in the first place when a user wants to log in and this actually brings us to a very big question should authentication be handled within the graphql server itself or is this out of band now some people say we should leave authentication entirely out of the schema in other words we shouldn't have login or logout mutations and that would mean we just have the decoded token data available in the context and we leave it at that and i would say in practice there's a very high likelihood that you would want to use some kind of dedicated auth service and perhaps even use something like off0 to manage your app's authentication needs but to keep things simple let's implement a login mutation here so we can get a sense of how the job would be created so we'll move on to our third step and i'm going to jump over to our type definitions and now that the code has been added we can see that we now have a mutation here for logging a user in and that mutation takes an email and a password as arguments because we're just going to use password-based authentication for this api you'll see that our login mutation also returns a string type and that string is going to be the json web token that we create inside of the resolver now moving on to our resolvers we'll take a look at what just changed here and you'll see that to log user in we find the user in our database whose email and password match the incoming arguments and then we use the json web token package to create and sign a jot for them and the first argument we pass into the sign method is an object containing the jot information we want to add to the payload of the token and because we're adding some custom info as a private claim to this token again we name space it using the url of the graphql api as a property with the user's permissions and roles inside of an object as a value as a second argument to the sign method we also have to pass in the same secret that we used before to verify our tokens and as a third option we can also pass in some additional data such as the unique subject value for the token which again is the user's id and a token expiration time and the signing algorithm we want to use all right we have one last step to run before we can try out our api so i'm going to hop back over to our type definitions and we can see here that we've now also added a viewer query and this query is going to allow us to retrieve an authenticated user's information based on the data in their token and as a little sidebar here it's a good practice to expose a viewer query that acts like an entry point for what authenticated users can do with your api and if we were to fully realize that here we could add a viewer object type to use as the return type for the viewer query instead of using the user type and then expose fields on that type that allow an authenticated user to query for relevant data now that's a bit out of scope for what we're doing here today but i encourage you to take a look at the github graphql api for a working implementation of this now lastly over in our resolvers we can see that in the resolver for this query we get the currently authenticated user's information by using their id value which is available in the subclaim of the decoded token in the context object parameter so with this code in place we're ready to try out our api so let's hop over to graphql playground and in graphql playground you can see that i've set up a login mutation to log in our neil armstrong user so if we go ahead and run that mutation we can see that we do in fact get back a json web token in the response so i can go ahead and grab that token copy it and hop over to a viewer query here and in the http headers panel here i'm going to add an authorization header and i'm going to set its value to be a string which starts with the word bearer has a space and then is preceded by the json web token and we can go ahead and run that query and we see that as we expect we get back the neil armstrong user all right this is looking pretty good as far as logging in users go but before we can finish locking down our graphql api we need to understand a few things about authorization because while we now have a way to identify users based on their tokens we still don't have any mechanism to limit api access to authenticated users and this is where authorization comes in now the most basic level authorization is letting users run queries based on whether or not they're authenticated and we're going to do this but we're also going to add finer grind authorization to our queries based on the permissions in the logged in user's jot now when adding authorization to a graphql api we have a few different options at hand we could directly check the authenticated user's id and permissions inside of each resolver but this wouldn't be very dry so let's just count that option as off the table instead one popular option for adding authorization involves adding custom schema directives to control access to various types and fields alternatively we could use a package like graphql auth to wrap our resolver functions explicitly with permission checks or we could use a package like graphql shield to completely abstract the authorization rules into a middleware layer and for our api today this is what we're going to choose now i'll jump back over to vs code here and the first thing we're going to do is run our next step and when this code is added we'll see that we now have a new file called permissions.js and this file is going to be where we manage all of the authorization rules for our api and before we import and use anything from graphql shield which i've pre-installed you'll see that we've created a little helper function here that we'll use to check if a decoded user token has a particular permission applied and if i go ahead and run our next step you'll see that we import a few things from graphql shield that are going to help us apply authorization rules to our schema and first we're going to focus on the role function which has the same parameters as a typical resolver function including the context and we're going to use this rule function to not surprisingly create an authorization role and the first one that we create here allows us to check if the user is authenticated by verifying that the decoded jot is present in the context and how this rule function works is that if we return false from the rule then the authorization will be denied so now we can add some more complex rules that will help us check if particular permissions have been assigned to a user and if i scroll down here you'll see that the can read any user and can read own user rules each check for their corresponding permissions and return false if they don't exist and we also have the is reading own user role which verifies that the id of the user requested from the query matches the id of the authenticated user now we're ready to add the final bit of code to this permissions.js file and when we do you'll see that we now call the shield function and into this function we pass an object whose shape mirrors our resolvers so for the viewer query we only require that a user is authenticated to run that query but for the user query we employ the logical and and or functions provided by graphql shield to check a more complex configuration of rules so for this case we allow a user to run the user query if they're requesting their own user and have the read own user permission assigned to them or alternatively they can view any user if they have the read any user permission assigned to them now last but not least we have some updates we need to make to our index.js file to actually add these permissions as middleware so when we run those changes you'll see that we now must import a few new things into this file most notably the apply middleware function from the graphql middleware package as well as the permission rules we just created and if i scroll down you'll see that we've also updated our apollo server config so that instead of passing in the type devs and resolvers directly we use the schema option instead and set it to the value of calling apply middleware and applying those permissions to our schema with this code in place we're now ready to jump back over to graphql playground and if i grab the authorization header that i used for my viewer query i can paste it into the http headers panel for the user query and if i run the query using the id of the currently authenticated user you'll see that we get back the neil armstrong user information as expected however if i run the query using the id of a different user we get back a not authorized error message as we might expect because this user does not have the appropriate privileges to view any user's account and that's it in just a few short minutes we've now successfully added both authentication and authorization to our graphql api so to wrap up i'll jump back over to my slides here and before we conclude i'd like to briefly touch on how we could approach handling auth when dealing with a federated data graph and the good news is that everything we just covered can be applied to the apollo server that you configure at the gateway level of your api so in other words you can extract the token from the request and apply it to the apollo server context just as we have and we only need to do a small amount of additional configuration to pass that incoming token data along to any implementing services so to do this when configuring the gateway we can make use of the build service option to extract the decoded user token from the gateway api's context and then forward it on in the headers of the request to the implementing services then in the apollo server constructor for the implementing service we can once again extract the user from the request headers and add it to the context for that apollo server and we don't need to worry about verifying the jot again here because that would have already been done at the gateway level of the api okay so that was a bit of a whirlwind tour of handling authentication and authorization of a graphql api's queries using apollo server and specifically we saw how we can handle incoming tokens and apply them to the apollo server context we also learned that a viewer query can act as an entry point for authenticated users in your api we also saw how we can keep authorization checks directly out of resolver functions to keep this layer as thin as possible and abstract those permission checks into a middleware layer and lastly we got a sneak peek of how we can forward an incoming token from a gateway api onto an implementing service using the build service option in the gateway config now if you'd like to see the complete code for this presentation you can find it in the first repo listed here and if you'd like to see a more extensive example of how to add auth with apollo federation i'd recommend checking out the second repo link and finally you can see an example of how to handle auth with a graphql api using passwordless authentication and a decentralized identity token in the third repo okay so thanks again so much to everyone for coming today and i'd be happy to take some questions now hi everyone i have some time to take a few questions now so i see if you've come in here um the first one i'll tackle is in the login mutation uh what if i need to redirect a user so that one came in from twitch and to that i would say if you need to redirect a user after a login mutation then this is likely something you're going to want to handle uh client-side after the mutation has successfully completed so that's going to be completely dependent on your specific use case with what you're doing with your client um we have another question here when it comes to auth is it better to lock down the whole api and have users hit a different endpoint to log in or do you think it's fine to have a public mutation and this question came in from kurt so again i'm going to say it depends um but i would say what i've typically seen with respect to login mutations is that these mutations have been public but i would qualify that with also saying that when i'm adding authentication and authorization to a graphql api i am most typically using a third-party service like all ceara for instance uh to handle all of those authentication concerns completely outside the context of the graphql api and then i control access to the api using middleware like graphql shield like you saw in the talk to then parse those permissions and roles as needed throughout the schema on a per field or per type basis all right so we have another question here from kurt which is uh what are your thoughts about allowing introspection of auth graphql apis so i would say it's probably almost certainly a good idea to shut off introspection in production so um yeah if at all possible leave introspection off um let's see what else we have for questions here uh over to twitch uh let's see so here's a an interesting one from arvel what do you consider to be the key advantages of using graphql shield over using auth directives and i would say um and this is a perhaps a matter of personal preference but i really like when the authorization rules are abstracted uh away from the schema and like completely away from the type definitions so that they can be handily separately in their own layer so perhaps if you are building a schema where you feel like it's okay to have those kinds of authorization rules uh intermingling directly with your scheme in the form of those directives that's fine but for me personally i like to have all of my authorization roles completely abstracted away into a separate layer let's see what else we have for questions here so here's a question from cindy uh how does field level auth look like uh do you recommend responding with a per field error so that will depend on what route you take with respect to handling authorization rules in your schema with graphql shields as was demoed in the uh the presentation there they kind of take an all-or-nothing approach to delivering an error to a user so if uh some kind of error occurs you get a generalized not authorized message um so it's i guess a bit ham-handed in that sense you can customize the error uh there is some mechanism in the api to do that uh but it is uh kind of one-size-fits-all in terms of delivering people errors so um again if you need more granular reporting as to like what kind of uh authorization error might have been triggered uh in your in your query then you might want to take a different approach there um let's see what other questions can we tackle here [Music] so there's another question from kurt here what do you think about using scopes for finer grained authorization and yes again that's from kurt and i would say that depends on your use case as well and just how you need to build out uh permissions and uh roles and groups and whatever else you might need to account for on the uh the authorization side of your authentication service so uh again it's i find it a bit tricky to answer that question in a generalized way so i'll say it depends for that one as well um let's see let's do one final question here [Music] um [Music] so i have a question here uh and i apologize if i mispronounced the name but i think it's from reagan and the question is at the initial load of playground how does the system allow for mutations with a token so as far as the example goes that i showed in the presentation that's something that needs to be manually added into the uh the authorization header in the http headers of graphql playground um okay so that's about all the time i have uh so we'll have to wrap it up here thanks so much everyone you
Info
Channel: Apollo GraphQL
Views: 19,832
Rating: undefined out of 5
Keywords:
Id: dBuU61ABEDs
Channel Id: undefined
Length: 25min 18sec (1518 seconds)
Published: Tue Aug 11 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.