Building an Authenticated GraphQL API on AWS with CDK - Enabling Admin Privileges and Multi-auth

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

what is CDK?

👍︎︎ 1 👤︎︎ u/MemeticDiffusion 📅︎︎ Apr 01 2021 🗫︎ replies
Captions
in this video i'm going to be building an aws absync graphql api using amazon dynamodb aws lambda amazon cognito and aws cdk as the infrastructure's code provider the api is going to allow both public and private access so users that are not signed in will be able to query and read items and signed in users that are part of the admin group will be able to create update and delete items the type of api that we're going to be building is for a simple e-commerce backend that's going to be essentially a product type so users that want to view the products can view them and admins can create update and delete them the general ideas and concepts here can actually be used across a lot of different types of applications so i think this is a really informative topic and i hope that you enjoy this and get a lot out of it with that being said let's go ahead and start writing some code so the first thing that we're going to do is go ahead and create a new directory i'll call this cdk products and here in the new directory we're going to go ahead and initialize a new cdk project so i'm going to say cdk init and we'll set the language to typescript once the project's been created we'll go ahead and install the dependencies using either npm or yarn or any package manager installation that you'd like and we're going to be installing aws cdk cognito aws appsync aws lambda and aws dynamodb and while that's installing we'll go ahead and open up the project in our text editor the code that we're going to be working with is going to be over here in the lib slash cdk products stack folder i'm sorry ck products stack file and here we're going to kind of write the entire stack within this one class but to get started before we start writing the code let's go ahead and import the imports that we're going to need so we're going to import cdk core aws cognito aws appsync dynamodb and lambda because these are the four apis that we're going to be working with next we'll go ahead and create our user pool now the user pool is going to be how we allow users to sign up using cognito and to create a user pool we just call newcogneto.userpool passing in the scope and an id and then we set all the settings that we would like for some basic you know sign up with username password and email finally we go ahead and create a user pull client and the user pull client allows us to interact with this application from a client side application so this could be a react or a front-end client using javascript or some native uh platform or in our case we're going to be using this client id to interact from the aws appsync dashboard later on the next thing that we want to do is we're going to go ahead and create our graphql api and to create the graphql api using authenticated access with cognito we first need to go ahead and create the cognito user pool which we've done and then next we're going to go ahead and create our appsync api to create an appsync api we call new appsync.graphql api passing in the scope and an id as well here we're passing in the name we set where our graphql schema is going to be located which is going to be kind of where we write our code for our graphql schema in just a moment and then we have our authorization config now this is especially um you know something we should take a look at for this video because typically a lot of the videos that i've done have a single type of authorization meaning that you can only sign in and use this api or interact with this api using either cognito user pools or api key but what we're basically doing here is we're setting a default authorization site meaning if we hit this api by default we're going to be setting it to api key but we can also set the headers to have an amazon cognito user pool type of authorization and then therefore we would need we need to be passing in the access token from the client and to do that we set an array of additional authorization modes and we set the authorization type to user pool and then we pass in the user pool configuration being the user pool that we just created up here so we set the user pool variable and we're kind of referencing it right here and that's really it on creating the appsync api stuff now we need to actually write our graphql schema where our graphql code is going to live for our you know for our schema so what i'm going to do is i'm going to go ahead and create a new folder called graphql and here i'm going to create a schema.graphql and this is going to hold the schema for our product type so here we have a type of product this is going to be kind of like an e-commerce item where we have an id we have a name a description all the other metadata like a price and a category and a sku that we might want to need um and then we set some um directives on to our type and here we're basically saying we want people that are both signed in using cognito but also using an api queue to be entered to be able to interact with this type so we're kind of decorating this setting the two different types of authorization access that we'd like to enable we're saying api key as well as cognito user pools we have an input type for creating a product and we have an input type for updating a product the input type for creating a product the id is not required because we're going to be allowing the api to create the id on the server if one isn't passed in but whatever items in addition to that are required in the type itself we are requiring them here so we're basically saying we uh have to pass in a name description price and category and we're kind of making sure that that's the same on the input and then finally for the update this is a little different because the only thing we need for sure in the update input is going to be the id because we need to know which item needs to be updated and then from there we're going to kind of allow users to modify any properties that they would like so we're not kind of saying you have to pass in anything else other than the id now we have a couple of queries and mutations for the queries we want the queries to be accessible by users that are signed in but also users that are not signed in so we are also attaching the at aws api key directive and the at aws cognito user pull directive because we want to have both public and private access for these queries the mutations are the ones that we only want signed in users to access so we're going to be looking at how that works in just a moment but we're creating three queries one for getting a product by id one for listing a product or i'm sorry for listing all products and the last one for getting a array or an array of products by category meaning we want to have an additional data access pattern this is going to be something we set up using a dynamodb with a global secondary index we want to be able to say okay let's just pass in a category and we want to hit a query using a gsi on dynamodb this is also going to be both public and private for our mutations we want to have create products delete product and update product now for us to define the authorization rules that we would like to have here we're going to decorate this mutation with the at aws cognito user pools directive and then we're going to pass in an array of cognito groups meaning that anyone that is within this group so if i sign up in a user and i put them in this group they're going to be able to hit this api with these mutations and they're going to be able to do these updates so this is kind of where our uh our authorization or fine-grained access control is starting to come into play a little bit here and then finally we're going to create a basic subscription listening for when a new product product is created we're going to be able to kind of subscribe to that event and maybe get new products as they come in in real time this isn't anything real important for this tutorial it's just something i like to do because it's pretty easy to do with appsync creating subscriptions okay so that's it on the actual graphql schema itself next we're going to go ahead and create the lambda function that we're going to be needing because what we're going to basically be doing is having our lambda function [Music] having the graphql api mapping the different requests into the lambda function and we're going to be writing our business logic in javascript so we're going to go ahead and create a lambda that i'm calling product lambda here we set the runtime we set the handler function which is going to be main.handler it's going to be located in a folder called lambda-fns and i like to bump the memory size up to at least 1024. this just makes the function faster and a cold start is faster as well and then finally we're going to now attach a data source to our graphql api by saying api dot add lambda data source passing in an id and the function that we would like to be a data source because with appsync you can have different types of data sources you can have a dynamodb table directly which means you have to write vtl but in our case we want to write javascript so we're going to be kind of using a lambda function as a data source you can also map to elasticsearch you can only map to serverless aurora as well or a nun data source which is something we're not going to cover today so now that we've set the the lambda data source we need to now add our we need to map our resolvers to this data source so we we need to basically say okay when someone says create products we want to map it to this function here so this is fairly simple all we need to do is now reference this lambda data source variable and say create resolver so we're going to say lambda ds.create resolver and here we have to pass in the type name which is going to be query and the field name which is going to be whatever field name this is going to be so in our case we have three queries three mutations therefore we're going to have three queries here and three mutations so here we're doing you know list products products by category mutations create update and delete product and finally we need a way to store this information in a database so we need to create a dynamodb table and then we also want to give access from the lambda function to the dynamodb table so let's go ahead and create the table to do that we're going to go ahead and say new dynamodb.table passing in again the scope and an id we're going to set really the main things to keep in mind here the partition key we want the partition key to be having the name of id that's going to be a string this kind of maps well to our product that has an id here next we want to go ahead and add that global secondary index so to do so we'll say we'll reference this product table and we'll say add global secondary index giving it a name and setting the partition key to be category so this way when we hit this query passing in a category we're going to be basically querying hitting that gsi getting that nice selection set of data that we're looking for finally we need to access this table from our function so we're going to say product table dot grant full access product lambda and this is going to go ahead and give access to the dynamodb table from our function and then we want to go ahead and add an environment variable so we're going to say product lambda dot add environment we're going to create a new environment variable called product table that we're going to now be able to reference within our function products i'm sorry it's going to be process.env.producttable that's because this is a dynamically created name that we don't really know the name of and to get that name we can just say product table dot table name okay so that's pretty much it as far as our infrastructure is concerned what we're gonna do now is to create our mapping of the different um let me go ahead and fix the indentation here the mapping of our resource names into a cfn output now this means a couple of things one when we deploy this all these values are going to be printed out to our console our terminal but we can also have the option of mapping these out into an external file this is typically really good if you're building a client application and you want to have those resources locally so in in my example maybe if i'm building a front end i might want to be able to have like the graphql endpoint i might want to have the api key and things like that as well as the user pull id and the user pull client id to have an authentication on on my client side project so that's kind of it as far as our infrastructure is concerned next let's go ahead and create our lambda functions folder so to do that we're going to go ahead and create a new folder here called lambda.fns and in our lambda functions folder what we want to do is we we're going to be working with a npm module within our lambda functions so let's go ahead and change into that new directory and we want to go ahead and do an npm init create a package.json and then we're going to add the uuid library the uuid library is going to be a way for us to create unique identifiers on the fly like i mentioned if someone creates a product and they don't pass in an id we're going to create that id for them so here in our package json we see that we have our uuid dependency okay so that's it there the next thing we want to do is let's go ahead and create some some of our our lambda functions and the first thing we want to do though before we create those let's go ahead and create a product type the product type is what we're going to be using to reference with typescript within some of our other code that we're going to write later so we want our product type to match our graphql schema pretty well where we have an id we have a name a description a price a category inventory values um all these values are kind of going to again be mapping the data that we're expecting from our graphql api the next thing we'll do is we'll go ahead and create our main function and this will give you a good idea of like what is all going to happen in our app the main function is going to be kind of where that event comes in and this is like our main handler function so to map all of the different graphql operations we're going to create separate files or separate functions for each operation so we want to get product by id function a create product function list products delete update and products by category and we're going to kind of modularize each one of those actions to dynamodb in their own folder or in their own file we're also going to import the product type because we're going to be needing to use that in just a moment down here now we're going to create our own appsync event object or our own appsync event type that has all the different fields that we're kind of expecting so we're going to have the info object the info object is kind of the graphql info object that gives us information about the graphql request that's coming through so we'll have things like the field name and the um things like the type of query that's happening whether it's a query or mutation and stuff like that but we we we're going to be actually using the field name in a switch statement right here where we're going to map over we're going to switch over the field names we're going to say if it's this operation we want to call this function so if it's get product by d let's call git product by id and so on and so forth we're also going to have the arguments the only three arguments that we're going to be working with are the product id for getting an item id the category for calling products by category and the product itself when we're creating a new product we also have the identity object that has the username that we could use if we wanted to but in our case the main thing that we're going to be working with is the claims um and in our case we don't even have to deal with this because the way we set up our schema we are actually using a directive that kind of says okay um here we go right here actually if they're in the admin group allow them to perform this action but another cool thing that you can do is like you can access not only the user's identity uh within the event but also that array of claims so if there's any other custom business logic that you might want to do here you can actually get the different groups that that user is in and have them and be able to do whatever you like with them and then here this is pretty straightforward we're going to basically switch over the field name and we're going to call a method or a function based on that field name so if they're calling create product we want to call create product if they're querying for list products we want to call list products and that's pretty that's pretty much it i would say this this code right here to me was really straightforward when i first saw someone kind of go this route so let's go ahead and create our dynamodb operations so the first thing we're going to do is we're going to create a create product function and here this is going to be what calls dynamodb put item so we're going to import the aws sdk we're going to create an instance of dynamodb document client we're going to import the product type and then we're importing the uuid library and then the create product function whoops i need to export default create products and the create product function we're going to say if there is a product id we we're just going to continue if there isn't let's go ahead and create an id for them for our parameters we're using that process.emb.product table that we went over in our initial creation of our infrastructure that we created that environment variable and then we're using the product that is coming in as an argument as the item and then we just call it document client.put passing in this params object and it is a promise so we'll be able we're able to await this and then we return the product because in our graphql request we might want to kind of have that data in the return values the next thing that we're going to do is we're going to go ahead and create list products and this is actually a really really straightforward dynamodb operation all we're going to need to do is create a params object using the environment variable of product table and we're going to call a scan operation against dynamodb this is just going to return every item in the dynamodb table i guess i'll open back main.ts because we can kind of go here and see um some information around how we're doing as far as our files being created that match up the things that we're importing the next thing i'll do is i'll go ahead and create get product by id.cs and this might need to be actually lowercase get product by id is going to take in a product id as an argument we're going to create a params object the id is going to be the product id and then we're going to call documentclient.get passing in the params that's a very straightforward operation as well i think a lot of these are pretty straightforward except for the update product which we're going to go over in just a moment which has a lot more logic going on so the next one we'll do is delete products delete products is going to be taking in again a product id and all the params need is the environment variable for the product table as well as the id which is the product product id and then we call documentclient.delete this deletes the item in dynamodb the next thing we'll do is create the products by category this is going to be where we this is going to be where we query the dynamodb table using the gsi the global secondary index and this is a little more complicated because we're getting the category here but we need to set up some key condition expressions expression attribute names and expression attribute values to work with the document client api so we're basically saying we want the key condition expression the key that we're going to be querying against we're going to basically say we want the field name to be category and then we want the category to be the category that's being passed in so we're essentially saying query on the key that is named category with something like shoes and then give us all the shoes that are coming back within that category it would be all the shoes right or if you were maybe wanted to create a query for sweatshirts or whatever else and that kind of makes up the params this is an api that i'm not too comfortable with like i don't love it but i've gotten used to it so if you're interested in learning more check out the document client docs and then we're going to call a document client.query passing in the params and you know pretty much the same as we've seen in some of the other different dynamodb operations that we work we've worked with the last thing we want to do is we want to go ahead and create update product dot ts and in the update product we're going to be allowing users to pass in n number of items to be updated so we don't really know what's being passed in other than we know that there needs to be an id so the product id will be there but we don't know what else is going to be there and in order to make this happen we're going to be kind of doing some logic where we map over the attributes that are being passed in um which are essentially the keys of this object that is the product so the product might look like uh we pass in an id and we might pass in the name we might not pass in the price but we don't pass in anything else and then the next time we call this we might be passing in the id and the description so essentially no matter what gets passed in there we're going to be mapping over the um the items there by making it an array and that's what happens when you call object.keys passing in an object you get an array and then we're going to map over the array and we're going to be dynamically building up this update expression the expression attribute names as well as the express and attribute values now this is a little more complicated than i want to kind of go through now but if you're if you want to learn more about how this works there are a couple of different articles that i've seen where people have kind of walked through how to dynamically create these update expressions but also this code is again available for you in the workshop materials so once we've built our params object dynamically we're going to call documentclient.update passing in the params again the same stuff we've been doing before so now we should be able to go ahead and try to um let's go ahead and go back to main and make sure that we're not seeing any errors okay all our errors have gone away we should be able to go ahead and test this thing out so what we're going to do is we should be able to go ahead and run npm run build and then cdk diff npm run build is going to transform our typescript files into javascript and cdkdif is going to give us a [Music] oops i'm in the wrong folder let me run that here yeah npm run build like i said is going to build our javascript file cdkdiff is going to go ahead and tell us what is going to be deployed when when or if we run cdk deploy so um the diff shows us all the stuff so we have a bunch of im rolls we have actual um you know resources being created all kinds of stuff now to actually build this we're going to run npm run build and cdk deploy now we could stop there but we what we would like to is let's go ahead and create an output file to create a a json file holding all of our exports so running this with the dash o flag and then passing in the file name that you would like to write to will create an exports file in our case we're going to call this cdk exports.json that will have our appsync api key our api url our project region our user pull id and our user pull client id so we could ingest this at this point within a client project so let's go ahead and run this and see what happens here we'll go ahead and choose yes to deploy the changes once everything is deployed we should see the outputs printed to our terminal and if we go ahead and open our text editor we should also see a cdk exports.json file with all of our resources printed out there as well now let's go ahead and test this out in the aws console to do so we'll first go to the cognito console i guess to go ahead and create a user and put them in the admin group so we should be able to look for the cdk products user pool and here i'm going to create a couple of users all right so we've created two users next let's go ahead and create a group the group needs to be named admin with a capital a because that's kind of what we set up in our api and now i'm going to add myself debit 3 to the admin group okay so now we've been added to the group the next thing we'll do is we'll go ahead and open the aws appsync dashboard and we'll look for the product api and the first thing we'll do is uh we'll go ahead and try just a really basic query because we have two forms of access let's keep it an api key which is public we should be able to go ahead and list products which is going to return an empty array but it at least should allow us to return the array without any type of authorization errors or anything like that okay so that operation works as a public user next let's go ahead and create a mutation for the product we want to pass in we're going to pass in all the things that we need so we need a name a description we want to pass in inventory price category and maybe like a sku so let's go let's go ahead and try to create this as a public user when we try to create this we get this unauthorized exception which is exactly what we expect next let's go ahead and try to sign in as one of these users the first user i'll try to sign in as is debit three this will go ahead and force us to go ahead and update our password since it was manually created and now i should be able to go ahead and create a product and i should also be able to list products because as an admin user i can do pretty much everything next i want to log out and just sign in with a random user and the mutation i might want to do is update product and i want to go ahead and pass in the id for the product that i'd like to update and we're going to return the id and maybe the description and maybe we'll update the description to say something like best shoes ever now if i run this mutation i'm getting an unauthorized exception because again i am not an admin if i log out and i log back in it's debit three i'm able to update the product and then when i call list products i see that my shoes are coming back and i have the new title and the new description actually there all right so uh that's about it we kind of walked through everything i wanted to show here i think using this as a guideline or like as a base you can do quite a bit i think the whole idea here is understanding how to access that user within appsync not only dealing with the directives on the schema but also working with the user and all of the user metadata within the event in the lambda function it's extremely powerful so this is actually one of my favorite stacks i really enjoy working with this stuff i really enjoy building stuff with both the amplify cli as well as cdk interacting with them from amplify client clients you know mobile web all that stuff so really fun stuff to work with for me so i really really appreciate your time and for checking this video out if you liked it definitely like the video and subscribe to my channel spread the word if you know anyone that might be interested in this stuff and until then i will see you next time thank you
Info
Channel: Nader Dabit
Views: 4,241
Rating: undefined out of 5
Keywords: aws cdk, serverless, typescript, aws, aws appsync, amazon dynamodb, aws lambda
Id: DOGadkjV7Hs
Channel Id: undefined
Length: 31min 41sec (1901 seconds)
Published: Fri Feb 12 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.