GraphQL Authorization With Auth0
Building A Real Estate Search App w/ GRANDstack: Part 8
Securing GraphQL APIs with Auth0 & GraphQL authorization schema directives. Working with JWTs and adding Auth0 to our React app.
Links And Resources#
- Code on Github
- GraphQL Authorization And Middleware
- Blog post: Authorization In GraphQL Using Custom Schema Directives
- JWT.io - decode JWTs tool
- Online JWT builder
- Hi everyone. Welcome to the Neo4j_stream. Today, we are getting back to our real estate search app with GRANDstack, and what we wanna focus on today is adding some authorization functionality to our application, both on the front end and the API layer to start adding some protected pieces in our GraphQL API, and then see how we can start adding some authenticated only features in our application, so if you watched last time, so last week, we kind of stepped away a bit from the real estate app, and we talked a bit about authorization options in general with GraphQL. And then we looked at a couple of features specific to the neo4j-graph integration, namely the GraphQL Auth directives and then passing in parameters through generated cipher queries. So if we take a look at the docs, most of that is explained on this page GraphQL authorization and middleware. And what I'd like to do today is to dive in now to applying some of these techniques to our real estate search application, and so that's what we're gonna do. So here's, let me drop the link to this in case you don't have that, so this is a link to the GitHub page for our real estate search app so far. I also added links to the videos from the stream so far, since we've been working on this app just in this stream, so you can, there's a YouTube playlist, or you can catch up on any of these individual ones. I added the last one from last week, exploring authorization with GraphQL and GRANDstack. I added that because it has features that are useful, but it wasn't specific to the real estate search app. So you can escape that one, this one today, I guess, will be part eight, I guess. Cool, so just kind of to review where we are at this point. So we have some personal data in Neo4j so I'm running this in Neo4j desktop, that's something that Neo4j browser, just a little bit, let's do a call db.schema.visualization. So here's the data model that we're working with. We have property nodes that have some detailed information stored as properties on the node, properties on the neighborhood, it can be in a city, a subdivision, and then we have one or more appraisals for each property, and if you take a look at some of these at random, let's see here's a bunch of properties in this neighborhood. And some of the detailed information we have such as the zip code, latitude and longitude, the polygon geometry of the parcel, how many bedrooms it has, that kind of thing. Okay, so we have that and then let's go over. I've opened up the project in vs code. So we can run start and this will start our GraphQL API, and also the react front end. So on top of our Neo4j database, we have a GraphQL API running, which we can access on localhost:4001/graphql that gives us the GraphQL playground. So here we're recording for all of the cities and the name of those cities. And then we can add more fields to that such as, let's look at first 10 properties in each one of these and their address and number of bedrooms, something like that, cool, and that if we take a look in terminal, drag this up a bit, as we're running those queries in GraphQL playground, we can see that those are being translated to cipher by the neo4j-graphql-js library, sent to Neo4j. And then that's giving us our data back through our GraphQL query, we can see in GraphQL playground. So that's the GraphQL API, and then we have the front end, which is running here, a question in the chat, "Are images of house listings put into Neo4j?" Oh, that's a good question. No, they are not, for a couple of reasons. One reason is that we don't have any images of these houses. I pulled this data down from the state parcel department, what's that called? The GIS department. And that doesn't include any data about images. But the other reason is that we typically wouldn't want to store like the actual image files in Neo4j, right. We typically don't wanna store, large blobs binary, large objects, or that sort of data in the database itself. Instead we might upload them to URL s3 or URL Cloudinary, something like that. And then we might just store it a URL as a property in Neo4j, but because we don't have the images, what we did instead, so let me click over to the search tab. So what we did instead, so here's some results, we can click on these, on the map and we can see some details about the property, and we do have some images, so where are these coming from? Well, we're using the MapilLary API and we did this, I think in part seven, oh yeah, so a couple of weeks ago we added photos using the MapilLary API, which is pretty fun, so MapilLary is kind of like, I don't know, like a crowdsourced Google street view kind of thing, and what that allows us to do is pass in some latitude and longitude to the MapilLary API, and it checks to see. Hey, do I have any photos of that area? Here you go, and you can see this kind of works. So we don't necessarily, aren't getting sort of images of each of these houses, but we're sort of getting the neighborhood, so it's a good approximation, but yeah, I think working with photos would be interesting to explore in the future because there's a few things there that we'd have to touch on such as like file upload and the LX seeking that's the s3 or something, but yeah, yeah, cool, good question. So that's the basic search functionality that we've implemented so far. So this front end query is our GraphQL API, which gives us the MapilLary images and some house detail information as we click through those. And then we also have this dashboard view. And so the dashboard view is showing us some aggregate information, average property value by city and then total number of properties, and then showing us some individual listings in a table. And so what we wanna do today is start to add some authorization functionality and authentication to this. And what we're gonna do is I think because there's a few interesting things in the authentication and authorization area to touch on, so the goal for today is to start adding some security and authentication requirements to our GraphQL API, and we're gonna do that on this aggregate data, so we're going to say the GraphQL API itself is still public. You can get individual parcel information. So you can do these searches without having to be authenticated into the application. But if you wanna look at this aggregate data, if you wanna look at average city property value, if you wanna look at the total number of properties in the database, then you need to be signed in to our application, and so we're going to add that protection to the GraphQL API, and then also in the front ends of our application. So we'll have some way for the user to sign in, to generate some authorization token that will then send to the front end to our GraphQL API. And then our GraphQL API is going to validate that, and if the user is signed in, they can see the data. If not, then they won't get it, and to do that, we'll use Auth0 as our authentication service. So that'll be the goal for today. And then some other like auth related functionality that we'll add is having some data specific to the user, right, so we'll start to think of user profile information. And I think what we'll do in this application is so as I'm searching around for properties and viewing details, when I'm signed in, I wanna be able to add, start adding these to like a saved results list, right? So these are the properties that I'm interested in, or that I wanna watch or something. And only I should be able to see my list of properties that I've saved, no one else should be able to see those when I log in, I should be able to see mine and add properties to that list. So I think that'll be the second sort of auth related feature that we'll add, I don't think we'll get to that today, and then the third one finally sort of auth related functionality is with adding personal information, so this could be, if you're a real estate agent, you can add new listings. So you have to have a certain role or scope when you sign in to the application, regular users shouldn't have that permission, maybe something like if you're the property owner, you can edit the details, maybe something like that, but basically adding in roles and scopes. So today we're gonna just deal with being able to sign in, protecting the API for some authentication requirements. You can only view these piece of data, if you're signed into the application, and later on, we'll start talking about how can I view only my data. And then we'll look at enabling mutations and creation, updating of data for just certain users with certain roles and scopes. Cool, so that is the plan, so in the chat, "File uploads seems pretty difficult with Apollo upload clients." Yeah, yeah, file upload, it's definitely something we should add to our list of things to tackle here. 'Cause that's probably a common need. Cool, so what do you wanna do here? So, well, we want to restrict this aggregate information. And so if we remember from last time, we can actually look in developer tools to verify this. Go to network and refresh our application. We can see that it makes three... trying to make this a bit bigger. Hopefully that's legible, so we can see that on this page, our application's making three requests to the GraphQL endpoint, one from each of these components. So we allowed these components to be responsible for their own data fetching. That's why there's three different requests. We can also move that up into just one GraphQL request for the whole dashboard view. But I think it makes more sense to have data fetching at these individual component layers, anyway, and we can look at the GraphQL request that they're making, so in this case, ApolloClient is handling sending the GraphQL request for us. And that goes as a HTTP post request to our GraphQL endpoint and the payload for that is an object that among other things contains this query, oops. So here's the query that we're sending. This looks like this is for the table. So it's going to the property field on the query type filtering on the first 10, ordering by total acres. Oh, we made a mistake there, it is filtering on total acres, not total value, okay, oh yeah, 'cause the value is out of order, and since it's most expensive, it's not really this expensive. It's properties are the most acreage, anyway, so that's the table then we're making... And the table on, I understand because that... we're still going to leave this one exposed this query. We should still be able to make this and calculate that table view, even if we're not signed in to the application, it's these other two that we want to add some protections and security, so in this one, property counts, so this is what's populating the component that says you have 50,000 properties or whatever that number was, and that is just querying the property count field on the query type. So it's just a query that, just a little bit, property count, okay, so that's a query field. So we need to protect that query field. And in the other one, this is querying the city values field on the query type, so city values and then for each city getting the name of the city and the average, and this is what is populating, where is that? This chart, so those are the two fields initially that we'll want to protect, a question in the chats. "Maybe some kind of filtering or dropdown could be nice as well, and maybe that could do some cipher logic to filter the list of properties." Yeah, definitely, yeah, so in our search view, we still have a bit of work left to do here, right? So one thing we're not doing is we're not searching the map from our property, it says, as we paint around we're still stuck to this one sort of fixed area that we're bringing in, and then the other thing we wanna do is yeah, like you said, add some drop-downs here that allow you to filter for things like price range, number of bedrooms, square footage of the house, that kind of thing, and then only show properties that are one within the bounds of the map and that meet your filtering criteria. And then, another thing we wanted to do is allow you to like draw on the map, so say you want like only houses in this area, I don't know, between like this mountain and these sort of highways here, and then only filter within the boundaries of that polygon that you drew. So, yes, so we definitely have some more work to add here on the front end. Cool, so those are the query fields that we need to protect, and if you remember, or if you caught last time, we were talking about these GraphQL authorization schema directives. So these schema directives allow us to add authorization rules to our GraphQL schema. So in this case, what we wanna do is add this authenticated directive, so that means, this must be authenticated request in order to access this type or field. So the first thing I'm gonna do is jump into index.js for our API, and I'm going to enable, so in the config object, we need to enable our authorization directives, so we'll say, auth, isAuthenticated:true and that will add the directive declaration for isAuthenticated, so we can then add that into our schema, and these are the two query fields that we want to protect, so we'll add isAuthenticated to property count and to city value. So that's saying to be able to access these fields, property counts, or city values, the request must be authenticated, and the other thing we need to do is make sure when we start the Apollo server, that our context object has access to the request. So what we're going to do is add a authorization token as a header in our HTTP request, and we wanna make sure that the schema directive implementation. So that, basically just those functions that are backing the logic of those schema directives, that they have access to those headers. So currently we're injecting a driver instance, and then this is for multi database, 'cause we can have more than one database in Neo4j. So if we wanted to also specify which database to connect to, we haven't used this functionality yet. Currently we're just using the default database. Anyway, that's why this part is injected into the context object, you remember the context object, this is an object that is passed to every GraphQL resolver. So at query time, this object is passed into, in this case, our neo4j-graphql-js library, so that it knows how to connect to the database, and now we're gonna tell it how to verify the authorization token. Okay, so context here we can also specify a function and this function is going to return an object and in our object, we're going to have the request itself and then you have to driver instance, and we'll keep passing along this Neo4j database instance as well. Okay, so I think that, yup, I think that restarted our server, our GraphQL server. So now if we make this request again, can read properly token of undefined, yep. It's 'cause we're not passing the token. So in the HTTP headers, we're going to add an authorization header and format is bearer token and then saml token, okay, so we're passing in... it's obviously not a valid token, and we'll talk about what it means to be a valid token in a second. But this is just some random gibberish. This is obviously not a practice token. So it tells me you are not authorized for this resource. 'Cause I've tried to access city values, which has, this is authenticated directive on it now, which is protecting that field, but I can still access other things, so if I were to say, give me the first 10 property addresses, I can still get that. I get a bunch of nulls 'cause our data isn't perfect. But now if I go back to our application, I can still search for data, refresh just to make sure we're not hitting a cached in point. So, I can still search for data, if I go to the dashboard, I get errors for our aggregate data, but I can still view the table. And if I look, let's look at the network tab here, take a look at those GraphQL requests. This is property, that's the one for the table. So that one went through, if we take a look at here, the property count, if you look at the response, gave us, "Cannot read property token of undefined." And if you did pass a token, it would say, "You're not authorized for this resource." Just like what we're getting here for city values, oops, city values, oops, there we go, okay. Okay, so how do I send a valid token to make this a authenticated request? Well, last time, if you remember, we took a look at this JWT Builder, so the GraphQL authorization, schema directives, this is a library that uses JSON Web Tokens, and the way this works is every request, every GraphQL request has a JWT, a JSON Web Token in the header. The server is then given a client secret, so a key, and it tries to decode that JWT with the key. And if it determines that, yup, this token has been signed by the private key pairs as public private key pairs that the request was sent by an application that generated that token, signed it with the correct key. Then it says, yup, you are authenticated. This is a valid request, so what we wanna do is generate a token and this JWT builder, this is just a nice way to build some JSON Web Tokens. I talked a bit about what a JSON Web Token is last time. It's basically we build up some claims. So there's some standard claims, so like the subject is kind of like who the user is? Tokens have an expiration date and who issued them, but then we can attach additional claims. So this may be something like the role that the user has, the scope that they've granted permissions for, something like that, and then all of those claims are added together in a JSON object and then a token is generated and signed from that JSON object. And we can inspect, so here's another great tool, jwt.io that allows us to paste in a token and take a look at the payload data, so here are all the claims from that token, and if I grab the key and paste that in here, then it can also verify that, yup, this is a correctly signed token. Question at chat, missed the last talk is there a recording? Yes, so if you take a look at probably the best, easiest way to get that is just looking at the README. I dropped in a link to the README there. And if you open the README and scroll down, sorry, not the README, the GitHub repo, if you look at the, README for the GitHub repo, at the top, there's a link to all of the previous recordings. There's this YouTube playlist, has them all, or you can check out the list here. And yeah, the last one we did kind of an overview of authorization options, so that would be a good one to check out. Cool, so what we need to do, so we've got our token, so let's go ahead and grab this, stick it in the header. So our GraphQL playground is going to send this token in the authorization header, which we've added here, but it still says you're not authorized for this resource. And that's because the GraphQL server doesn't know how to cryptographically verify that that token is valid because I haven't told it what the key is to use to decode it and to do that, we need to create a environment variable to specify that. So we're gonna do that by adding an entry to the .env file in the API directory. So we're going to add JWT_SECRET= and here's our key, and so this will then be added as an environment variable so that our GraphQL server will know to decode the token using this key. And this is just a convention of that GraphQL auth directives library to look for an environment variable named JWT_SECRET, I think we need to restart the server 'cause I don't think it's watching that .env file for restarts, okay. So GraphQL servers ready, so let's try this request again. Cool, and now that we have specified the JWT_SECRET and we're passing this token as an authorization header, now we get back our data that is protected by the isAuthenticated schema directive. Cool, so we've protected that data at the API layer, but if we go back to our application, we still can't access that data in the dashboard. We still get errors for those two components and we also don't have a way to authenticate. So what we need to do now is, well, first of all, we need a way to sign in to the application and we need some way to send like a valid token that our server can verify, and for this, we're going to use Auth0, has anyone used Auth0 before? Any folks familiar with Auth0? If you haven't used Auth0 it's basically an authentication service, you could also think of it as an identity provider, so it allows you to implement what they call a universal sign in, in your application. So it allows you to easily add sign in with different auth providers, so things like sign in with GitHub, sign in with Twitter, sign in with Google or create a username and password, so it takes care of all of that and gives us just like a token that has the user identity that we can use in our application, it has lots of other features that we can do more complex things with, but that's what we're gonna use it for today, I like Auth0 to use especially with GRANDstack because it by default uses JSON Web Tokens, which is what our GraphQL authorization directives library uses, and also has a free tier. So we can just sign up for Auth0. I think I used GitHub to sign in last time, that looks fine, and the free tier allows us into so many applications and so many sign-ins to each application, something like that. I'm not sure what the free tier, not sure where the cutoff is, but it should be good enough for what we wanna do today, so I'm gonna click on create a new application, let's call this Willow-GRANDstack, and this is gonna be a single page web application using React, the reason they ask us that is just for, I think the documentation. "Do you want to download a sample?" No, so here's sort of the steps we're gonna go through. I'm using a previous example on using Auth0 with GRANDstack, where is that? On the GRANDstack blog perhaps, yeah, this one. So this blog post, which I'll drop a link to in the chat. And then I think, so this shows how to use the schema directives instead of Auth0, kind of like what we're gonna do today. There's a link to the sample code, here it is. And this demo app is kind of a similar thing. So it has different views that users must either be authenticated or have like an admin role to view this demo app. When I created this one, I'll drop a link to the code here to, if you wanna take a look at that. But what I was gonna say is this one uses sort of the Vanilla JavaScript, so if you take a look at some of the React code here, so we have to create like this auth class that saves the token in local storage and sort of implements the auth flow in our application. We have to add a few other things to the application. Let's see, du, du, du, Auth0, not in this clip. Anyway, what I wanna point out here is this one used the Vanilla JavaScript Auth0 library. However, I think since then Auth0 has added this React library, which is really quite nice. So it makes it a lot simpler, there's a lot less code that you have to write, so for example, it gives us this Auth0Provider that we can inject into the component hierarchy, and then we have some Auth0 hooks, this useAuth0 hook that we can use throughout our components have access to the user object, and things like that. A question at chat, "Do you think Auth0 is better than Passport.js?" Yeah, that's a good question, so Passport.js is a very popular middleware. For Node.js and it has plugins or we call them strategies for lots of different services, I've used Passport.js. It's been a while since I've used it. My understanding, I guess, is Passport.js is not quite a service and it has all of these sort of strategies, but it doesn't give you, just sort of like these really nice drop in, view all of your user information and in a dashboard ad, more complex rules to assign scopes and things like that, that you get with Auth0. So that's kind of my understanding of the differences there. If you're looking for, I think just kind of like a drop in service implements, all this functionality Auth0 is very useful. Cool, so, okay. So what we wanna do now, so we've created our application that we called Willow-GRANDstack here. And what we wanna do is now in our React application, add some functionality that will allow the user to sign in. So the first thing I'm going to do is CD into web-react and NPM install Auth0-react, and go as I closed this tutorial thing, this is the right one, so this is going to give us access to the Auth0Provider, we can inject into the component hierarchy, and then it will also give us access to like log in, redirect functions, and an Auth0 hook that will allow us basically in any component to access user info, including that JWT token that we need to send in every GraphQL request, so let's see, I think the first thing that we should do, so I think we're done with the API for now. So in web-react source, we want index, yeah. So the first thing we wanna do here is let's import Auth0Provider from Auth0-react. And then we will wrap our ApolloProvider, I guess, with the Auth0Provider. And then we need to add some additional information here. So the Auth0Provider takes domain, a client ID, and a redirect URI, redirect URI, well, this is gonna be wherever in the window.location.origin, where we're coming from and the domain you can get from the dashboard, let's add these to our .env file. So create React app has this really nice convention of sort of allowing you to define environment variables that at build time just get sort of rewritten. So REACT_APP, what should we call this? AUTH0_DOMAIN is that copied from our dashboard and REACT_APP_AUTH0_CLIENT_ID, which identifies, I guess our Auth0 application, now is that, and then domain, this is going to be process.env.REACT_APP_AUTH0_DOMAIN, and the client ID process.env.REACT_APP_AUTH0_CLIENT_ID, I think I called it, yeah. And that is wrapping the ApolloProvider, cool. Okay, so that injects Auth0 into the React component hierarchy, so then what we should be able to do is now let's take a look at our application. What we need here is like a login button that's then going to walk us through the auth flow and generate a token for that user and so on. So if we go to maybe app.js, yeah, so in here, let's see, I don't want to import useAuth0 from Auth0-react. And then inside the component, we can use the hooks. So in our app component about here, we can say, so what do we need? We need the login with redirect, so login with redirect as a function, log out as a function is authenticated as a Boolean, useAuth0 hook. Okay, so now in our component, we have this login with redirect function that we can call. That's going to handle redirecting the user to Auth0 authentication flow, and then redirect from there, back to our application, although we need to configure that in the Auth0 dashboard, we need to specify the allowed callback URLs, and the log out URLs and the allowed web origins, well, that's easy. The single page app that we haven't deployed anywhere. So for now, it's just gonna be a localhost:3000 for all of these and save changes. Okay, so this is saying for this Auth0 application, I can use this login with redirect function, to redirect to the auth flow from an application hosted on localhost:3000 and then redirects to localhost:3000, by passing in the user token that we generate. Okay, so I have these log in, log out functions. Let's add them in this app bar here in this toolbar. So I have this typography welcome to GRANDstack App. And then I just wanna add, oh, like a button here. I think I have imported button from material UI. Okay, but it needs to be log in. If I'm not authenticated, it needs to be log out if I am not authenticated, so if not isAuthenticated, then show a button color inherit 'cause we want it to be white, so we want it to inherit the styles. And then the un-click handler is just going to call login with redirect, and then we'll say login. Okay, hope we're not, NPM run start. Let's give that a try, so we're building the React app. Oh, actually I was in the wrong director. I think that just started, yeah, web-react. I don't have it running in another session, do I? No, yeah, that was just web-react would also need to start our GraphQL server, so most of the time we can just leave everything running and we have a watcher that both for the GraphQL server or the front end, if we change any files, it should restart those. But I had restarted it because I changed the .env files, I guess also because I installed a new dependency. Okay, cool, so now I have a login button, so let's see what happens when we click that. And it says sign in Willow-GRANDstack. Yup, sign in with GitHub, authorize, Willow-GRANDstack is requesting access to my GRANDstack tenant, access to my profile and email. Sure, that's fine. Give Willow access to that, okay, so now what? So I don't have the login button anymore, so that must mean that I am authenticated. Okay, so now what do we need? Well, in the case where I am authenticated, then let's give the user a log out button. So the un-click handler will be log out. It will say log out. Okay, so log in, sign in with, GitHub. Cool, now the button says, log out. So log out, now I can log in again. Okay, so that's fun, so I can log in and log out. I get some user information, so like a user object has been created and injected into the React component hierarchy, I can access that, so let's see how we can use that. So let's say in the case where I'm logged in, maybe we should show a user profile information, perhaps. So this is where we are adding our router and choosing to show either the dashboard or the search, so maybe just above this, we'll add a profile component, and of course this doesn't actually exist, so let's create in components, a new file called Profile, and this component is just gonna show some information about the currently signed in user. So it's gonna be a React component and we will use this useAuth0 hook, and Auth0-react, it has to be a functional component. What's it gonna do? Well, you can grab the user object. And whether or not we're authenticated using the useAuth0 hook, syncing up and says, maybe the user can upload an avatar in their profile. Yeah, well what's nice is with Auth0 and with those social sign in functionality, is that we get an avatar for free. So this user object will actually contain a avatar image that we can use. Okay, so what are we gonna return? Well, if we're authenticated, then we will return a div and image, so, yup, we'll use that avatar it is gonna be user.picture, reset password could be nice too, yup, well see what's nice about using Auth0 is we don't have to deal with a password reset. So because it's authentication as a service, if the user wants to reset their password, if they've created an account with Auth0, Auth0 handles that, all that we have to think about is just kind of working with those auth tokens and user objects, so makes it easier on us. Okay, the user name and what else can we show? I guess username and, oh, and we also get emails. So we can say user.email, I think. Okay, so profile here, oh, and then we need to import it. Import profile from components profile. And we have a typo missing, those in quote there. Does not contain a... Oh yeah, I need to export a component export default profile. Okay, so log in, sign in with GitHub. And, oh, okay, there we go, there's my profile component. So giant picture of me, so this is my GitHub avatar. So that's where that image came from. So, because I used sign in with GitHub, we're using avatar from GitHub and there's my name from GitHub and my email address. Let's maybe clean this up a bit. You see a giant user avatar, I think there's an avatar component from material UI, you can also use a paper and what's the other one, typography from material UI core. So let's see, so let's change this div to a paper component that gives it kind of like this, and see these like you can, so I'm pointing out my screen, you can see this, it gives it like a sort of this white, rounded edge background to look like something that represents a piece of paper in material UI. Anyway, let's change this to the avatar. I think that like shrinks it down and gives us a rounded image. And then we'll change these to typography components sort of spring in some of the nice styling from material UI, so log in, sign in with GitHub. Okay, that looks better, but we're like under the banner there a little bit. Well, let's cheat a little bit and just give it some padding maybe, something like that I think should work. Login, GitHub, there, that looks okay. We're not gonna win any design awards, but that's fine. And that comes through as we switch to our search component as well. Cool, so we can now sign into our application. We can show some profile information. What do we need to do next? We still can't see the components that we're protecting here, maybe let's change these. So instead of error, how about sign-in to view? So that's readings charts and, oh, I still have the user count and ratings, should rename those. That comes from the starter project when we converted that. First we had business reviews and we started to add in our real estate functionality, sign in to view, okay, so it says sign in to view, so this is, you know, you've probably seen these types of applications where you get some information for free as kind of a teaser, but then if you want to view more information or more functionality, you need to at least just sign in. So that's kind of what's going on here. It's encouraging users to sign in to the application to view this aggregate data, okay, so now what we need to do is send that auth token in our GraphQL request. So when we did this in GraphQL playgrounds, we added the authorization header that has our token. So we need to make sure that we're sending that now from our React application, so how do we do that? Well, let's jump to index.js. So here we're creating our ApolloClient instance and we're injecting that into the React component hierarchy here, and we're also injecting in our Auth0Provider. So what we wanna do is when we create our clients, we want to have the client add this authorization header to every request, there's a couple of different ways to do that with ApolloClient, ApolloClient has this concept of links. So if you look at Apollo link http, so a link allows us to create things, kind of like middleware, where we can do some operations to the request, as it goes through this middleware pipeline here though, we're just sort of sending or just specifying the URI, we're not actually explicitly creating a link 'cause we don't need any sort of like custom logic with the link middleware there. So that's what we wanna do though, basically is tell ApolloClient to grab a token from somewhere. That's the next part we need to figure out and add it to the header, so to do that, this is going to look something like this. So let me request, it's gonna take an operation and then we're going to call operation set context. And here we're gonna specify the headers. So we wanna add an authorization header. That's gonna add some bearer token, but we need to figure out where this token is going to come from, so the Auth0-react integration, specifically useAuth0, gives us a function called getAccessTokenSilently. Let's go there, useAuth0, getAccessToken React, call an API, yeah, so in this case, we need to grab an API token, an accessToken, and here we're going to use this, getAccessTokenSilently function, to give us our token. Oh, the other thing that we need to add is the audience. So the audience, because now we're gonna be authenticating against our own API, we need to specify that in the Auth0Provider, so Auth0 has this concept. Let's go back to the APIs, Auth0 has this concept of the Auth0 management API, and that is what we want to add as an audience here in our Auth0Provider. Just saying that the token that we generate needs to be validated against this API. Okay, so to use this, getAccessTokenSilently, we need a little bit more set up here. So we're gonna use a couple Apollo or React hooks here. So we're gonna use a callback hook, the useEffect hook and the state hook, so we're bringing in the Auth0Provider and they useAuth0 hook, and what I'm gonna do is create a new component here, let's call it AppWithApollo, and this component is gonna do a few things, it's going to have some state, so accessToken, setAccessToken. So it's gonna use the state hook to keep track of the accessToken as state. And then we want this getAccessTokenSilently, and loginWithRedirect. Okay, so that's a function you need to call. And then we're gonna create a callback hook. I'll talk about this, what this is in a second, but we'll call this, getAccessToken is useCallback and that's gonna be aSync function. So we'll try to get the token, getAccessTokenSilently, let's go ahead and log it and if we get it, and then when we get it, we're gonna call this setAccessToken function that we'd get back from the useState hook to update the accessToken state variable. So setAccessToken passing in the token. So that will update this value, the accessToken state variable. We'll throw an error if we don't have a token. So I guess in the case where we're not authenticated, and in that case we might want to force the user to sign in. The problem with this though... So, this would be good in the case where we have to be signed in to the application, but in this case that doesn't quite work for our use case because we still want to be able to allow the user some functionality, right? So they can search for properties or things like that. So, in this case, let's just comment that out. Okay, so we won't force the user to sign in. We have this callback hook, "What is a callback hook?" So a callback hook gives us a memorized callback. So that means we'll only do this once. Oh, we also need to pass in the dependencies of this. So pass in, getAccessTokenSilently, and loginWithRedirect, okay, and then to call this. So this will only happen once. 'Cause it gets normalized in the callback hook, but this is a side effect. So that's why we need to use the useEffect hook to call this, 'cause we want this to happen when we re-render or when we render getAccessToken, not silently getAccessToken to call our callback hook and we need to pass in the dependency for that, which is getAccessToken. Okay, and now we'll have an accessToken that we can pass in to creating our ApolloClient instance. So what do we need to change here? So we're creating the clients set context. Oh, here, if we have an accessToken. So the case where we have an accessToken, we'll set the headers, and the closing program there. Oh, no, don't want it here, I want to only call operation set context, oops, only call operations set context if we have an accessToken, and this should be our next step. Okay, so create a new ApolloClient URI, we're grabbing from environment variables. And then the request if we have the accessToken calling operation that set context and adding an authorization header with the accessToken. So then this component needs to return ApolloProvider with the client and then wrapping our app component. And then we will down here instead of calling it ApolloProvider in the app we'll... What did we call this? AppWithApollo. Okay, so what should be happening now, is that when we are signed in, our accessToken is grabbed thanks to the Auth0Provider and this getAccessTokenSilently function that is passed to our ApolloClient instance when we were creating it, it is adding an authorization header with that token and the header, so then each request that we make to our GraphQL API, when we're authenticated includes this token, if we're not authenticated, then it doesn't include the token. A question in the chat, "Does Auth0 handle refresh tokens as well?" Yes, I believe so, although I don't know too much about that, maybe we can explore that in a future session, but yeah, I believe it does. Okay, so let's take a look at our application. So, this looks to be working, it's like we make GraphQL requests, we didn't seem to break anything there. That's good, let's go to our dashboard, let's sign in, sign in with GitHub, requesting access to your GRANDstack tenant, yup. Okay, and we get the profile view. So that's good, we can tell that we are authenticated. We go back to our search view, it still works, but we're not seeing our chart, not seeing our aggregate data, why not? Let's take a look at developer console in the network. But first let's see, so you're logging some property data. That's fine, tracking error, "You're not authorized for this resource." Oh yeah, by the way, here's the token that we're using, it's being passed and we can verify that's being added. So if we do a refresh. And if we take a look, oh, right, I need to... that refresh, yeah, so I was no longer signed in, so I need to sign in. Okay, now I'm signed in. And if I look at my GraphQL requests, I can see that, yup. This authorization header is being added to my GraphQL request, so that's good. This is the one for the property information. So this one is giving me actual data back. Do I look at this one, which does have the auth token. This is for property count, look at the response. It says, "You're not authorized for this resource." Why is that? Well, if we go back to our API .env file, this is the secret that we are telling our GraphQL server to use, to decode our auth tokens. And that's the wrong secret. So it's trying to use that secret, remember, that's the one that we got from this online JWT Builder, which is no longer what we're using. Now, we need a secret from Auth0. So if we go to our dashboard, Willow-GRANDstack, so it's not this client secret. If we go down to show advanced settings and OAuth, and if we take a look here, we're using the RS256 algorithm. So this says, specify the algorithm used to sign the JSON Web Token, if we use HS256. Then it will be signed with the client secret, with this client secret, but if we're using RS256, we'll use your private signing key and verify using your public assigning key. And because we're using RS256, I think is the default in Auth0. We need to use this public certificate as the secret instead of the client secret. So we'll drop that into JWT_SECRET. This is a bit of a quirk, I think, with .env. I think there's probably an easier way to do this, but for multi-line I've found we need to add new line characters and then pull out the line breaks. We could read this from a file or something, but because we're setting all of our other environment variables with .env we'll just add these new lines and make this all on one line in the .env file. But if anyone knows how to handle multi-line values with .env definitely let me know. Okay, so there's our new JWT_SECRET in our .env file. We need to restart because that won't get picked up by the watcher automatically. Okay, so that's building. So let's see if this works this time. Okay, so log in, sign in with GitHub. Cool, and now we're signed in. We can now see our average city property value charts and total properties, if we log out, then nope, we don't see that. Cool, so that is what we wanted to accomplish for today. What else can we do? We can verify well, maybe if we look at the network tab here, if we log in our data back, if you take a look at the GraphQL requests, we can see, here's our auth token being added. This is a request for property counts and you can see we're getting back data rather than the you're not authorized message. Cool, so that is what we wanted to accomplish today. So we've added the ability to sign into our application using Auth0, we've protected some fields in our GraphQL schema using the isAuthenticated, GraphQL schema directive, we've hooked that up to our Auth0 JWTProvider so that we can authenticate requests against our GraphQL point. Cool, from the chat, "So they're going to be more auth related stuff in the future?" Yeah, so this is just the first step in some of the auth functionality we wanna add. I think the next thing we wanna add is while we're searching for properties, so I'm logged in, I'm searching for properties, and I find one that I like, and I wanna save it to a list, so I wanna be able to save information that is just private to me. So no one else should be able to see that, when I sign in, I should see my list of saved properties. That's the next thing, and then after that, in terms of auth related things we wanna add, we need to be able to update and create new property listings, so when we get to that part, we'll need to look at roles and scopes. So you're not just a regular user, you're like an admin or in this, in this example, maybe you're like a real estate agent. We need to figure out how to grant that specific permission to users, so that they can have permission to update and create data, but other users can't. So I think those are the two things that we might add in terms of auth going forward. "Custom resolvers is something I might wanna see if possible at some point." Yeah, definitely, so we did a little bit with custom resolvers when we did the MapilLary API. So for example, this is the resolver that we added for being able to fetch images using the MapilLary API, but we'll also maybe do some custom mutations, I think might be interesting, so maybe when we get to... but we could do that with the saving of the property list. That could be a custom mutation, or maybe when we get to adding new property, new parcels. But yeah, definitely a lot to add there. Cool, so I'm gonna push this up to GitHub. If you have any questions, feel free to ask in the chat while I push up to GitHub and then we'll call it a day. So source index, GraphQL schema, web-react slack, package.json, source app, userCount component, web-react, source index, through editor Auth0Provider, and then we created a new component profile. Cool, I think that should be everything, cool. So git commit, this was part eight adding Auth0. Cool, so this work that we did today is now up on GitHub repo, so you can follow along part eight, adding Auth0, so this is all the work we did today and the recording will be on YouTube, which I'll link in the README here. And yeah, I think for next time, I think we'll probably start looking at that saved search functionality since we're kind of on a authorization related kick as well.
Subscribe To Will's Newsletter
Want to know when the next blog post or video is published? Subscribe now!