Introduction to GraphQL Authentication Solution
This course has been updated! We now recommend you take the Server-Side GraphQL in Node.js course.
Transcript from the "Authentication Solution" Lesson
>> Scott Moss: Hopefully everybody have enough time to lock down those resolvers using our authentication. So let's just go ahead and get started with that. So, first thing you wanna do is head over to Server and we want to use the authenticate function from the utils auth. Pass a request and hopefully get a user back, so what am gonna do is, I'm gonna say, const user = authenticate method here and I'm gonna pass the entire request object like this.
[00:00:35] Let me go ahead and import that.
>> Scott Moss: From util/auth as the authenticate method. There we go. So once we got that, we can go back down here. Authenticate is async, so we'll put an await here and we'll make this context async, like that. And then we'll just replace the null with whatever authenticate returns.
[00:01:04] So there we go. Now, we're authenticating the user based on the request which, if you look at the authenticated method, it's just looking for an API key. If it does find an API key, it finds the user associated with that API key. Since it is a public API we're using API keys.
[00:01:21] So we have that, the next thing is to go ahead and lock down our resolvers. So the test and the task we're asking that you lock down the queries, so in this case product and products, so that they do not allow requests that come in that don't have users.
[00:01:38] So basically because we created that context object that has a user property on it we could check the context object here. So if not context dot user, and that's because the authenticate function will just return undefined or null if it didn't find a user, we can safely check, we safely know there is no user in the context object.
[00:01:59] Then you're not authenticated. So then we'll just throw a new authentication error like that. And the products query is very much the same thing so we can just paste that there and then we'll run some tests.
>> Scott Moss: So if we look at our test here, looks like we got the product and the products query working fine.
[00:02:31] The next thing we have to update the mutations to not only look for the user not being there but also making sure that the user has the right role. So we're authenticating and we're authorizing at the same time. So what we can do is go to the user products here.
[00:02:44] You see there's a note here says, use fake id here to create the ObjectId. And that's just because that was there before we had to authenticate. But when you authenticate you're going to have to change this because there's a test for it. So if you didn't run into that, if you didn't get to that test yet then you'll see.
[00:03:02] So for the new product basically what we want to do is, actually I'll just leave that fake one there for now until we fix that test. We wanna go ahead and check for not a user, right? But we also wanna make sure or if the user is there, what the role, and there's a .role for a couple of reasons.
[00:03:24] If you go, one, if you look at the user Model there is a .role here which is a type of string. And it's a enum of either a member or an admin. So it can be a member or an admin and it's required. So we know that's there.
[00:03:42] So if the ctx role.user does not equal the admin role which you can get from up top right here, it's already imported for you, roles. Which is like a constant object that has it. So if it's not roles dot admin, then we also want to throw an error.
>> Speaker 2: [INAUDIBLE]
>> Scott Moss: Yeah, sorry, yeah. There we go. So ctx.user.role does not equal roles.admin. So we do the same thing for all the mutations that we have. And we'll go down to updateProduct, we'll put that there. And we'll go down to removeProduct and we'll put that there. We'll run the tests.
>> Scott Moss: Cool, so looks like, let me see, that's the file, I'm having a Mongo error here with the typology being this short so I'm gonna run it again.
>> Scott Moss: Okay, Mongo is just freaking out here. So I will fix the last one and see if that corrects the Mongo issue I'm having.
[00:04:53] I saw some other people having this issue with Mongo but basically Mongo is being disconnected while it's doing an operation so it's like a race condition. So we'll fix the last test, which was newProduct users, the auth user for createdBy. So I'll do that. So newProduct uses the auth user for the createdBy, right now we're using this fake one.
[00:05:17] So we'll replace that with a real one from the authenticated user, which will be ctx.user._ID. So we'll replace that, run the test and boom. That fixed all the tests so everything's working now. Who got all the tests to pass? Nice, we got a couple. Who was really close?
[00:05:42] Really close? All right, that's what I'm talking about. I think it's pretty cool to have flexible authentication using GraphQL. I mean, you could authenticate only field level which is kinda ridiculous. I really like that. And you can abstract away so many different things, different roles. If you're like a B to B multi tenant app, this is amazing because you can have so many restrictions and different roles on different pieces of data, it's kinda crazy.
[00:06:03] So I really like it. Anything else?
>> Speaker 2: Can you go back to the server.js just once?
>> Scott Moss: I sure can. Server JS right here. So this authenticate function is asynchronous. It takes a request object which is is given to us by the context object from Apollo Server. And then we pass our request object.
[00:06:25] Its asynchronous, so basically what authenticate does is it looks for an authorization header for an API key. If there is no API key it just returns undefined. If there is an API key, it looks for a user with that key and then returns whatever the data base returns, which will be either a user or undefined.
[00:06:41] So that means user will always be a user or undefined, and then we just pass that along. So whatever object we return here, that's what's gonna be the context for all of our resolvers. So if I put something else here.
>> Scott Moss: That's gonna be on context.data. So this object is whatever, it's what the context is gonna be, and that's why it's called context.