Okay, So next up, we're going to add authentication to our gateway microservice so that when requests
come in to our GraphQL server on the Gateway, they will be authenticated using our normal JWT process.
So to do this, we're going to have the gateway microservice send a request to the auth microservice
using our TCP transport layer and the Nestjs microservices package.
So let's go ahead and set this up.
We will go into the Gateway module and add a new section to the imports array where we'll go ahead and
import clients module dot register async, and then we'll provide an array of clients that we're going
to configure.
In our case, it will just be the auth service that we're going to configure.
So let's import the auth service from app slash common as our injection token and then we will use the
use factory to set up the configuration of this client.
So go ahead and make sure we inject the config service and now we can expose the configuration object.
Also, don't forget to provide the inject property where we actually inject the config service so that
it's available in the use factory.
So we'll start off with specifying the transport layer.
In our case we're going to use transport dot TCP and then we'll specify the options.
So we'll use our familiar options where we have the host and we'll use the config service dot get or
throw to extract the auth host and then we'll get the port using config service dot get or throw auth
port.
So with this, we'll have to go into our M file and make sure we provide the auth host and auth port.
So let's go ahead and do that.
We'll specify the auth host is equal to auth, which is the service name we define in our Docker compose.
And then we have the auth port which we know will be listening on Port 3002, which we can confirm by
going into the auth dot env and we want the TCP port at 3002.
So now that we've correctly set up the auth client, let's go ahead and in the Gateway source folder
we're going to create an auth dot contexts file and this is going to be a function that our gateway
microservice will call each and every time a request is sent to our GraphQL server.
And in our case, we want to reach out to the auth service to authenticate a JWT token and then attach
the user that gets sent back on the current request so that our downstream GraphQL services will have
access to it using our current user decorator.
So let's go ahead and export a const called auth context and set it equal to async.
And in here we'll get access to an object that has the request on it.
So we'll go ahead and open up a function.
And now let's open up a try catch block.
Where we catch any errors that occur.
And firstly, I want to check to see if we have any errors.
Well, if we have any errors, let's re throw this as a new unauthorized exception from Nestjs Common
and we'll pass the error back into this unauthorized exception.
Now we need to get access to the auth client in this function.
And since this function is being run outside of the nestjs context, we need a way to actually pull
or register the auth service right in this function.
So in order to do this we can create a new file in the source directory that I'll call app.ts.
And in here I want to create a new let called app that will be of type I Nest application.
And then I want to expose a function called set app that our main.ts will call when we bootstrap the
application so that we set this application and make it available to our auth context when we need it.
So we're going to have this function take in an underscore app parameter that will be of Type I nest
application and all we're going to do is simply set this variable equal to the underscore app.
Finally, let's go ahead and export the app and the set app function from this file.
And now in our main.ts, after we bootstrap and initialize our application, I want to call, set,
app and pass in the app that we just created so that it will be set and available later on in our auth
context.
And now we can actually use this app in the auth context because it'll be set at this point and we can
get access to the auth client now by declaring a new const called auth client, set it equal to app,
which we import from dot slash app.
And on the app we have the get function which will let us get access to any provider in our application.
In our case, we know this is going to be of type client proxy so we can specify the type and then pass
in the auth service injection token that we registered in our client module in the Gateway module here.
This injection token is matching the one we're pulling on the app dot get call.
So also make sure we import this from app slash common.
So now that we have the auth client, we want to actually reach out to the auth service and verify our
JWT.
So to do this we'll declare a new const called user and set it equal to well, we want to convert this
auth client call to a promise because by default this auth context will only work with promises and
not observables.
So to do this we'll call await.
And then in order to convert an observable into a promise, we can use this function called last value
from rxjs.
And now inside of this function we will pass in our source observable, which in our case will be auth
client dot send.
And then we're going to send to the authenticate route.
And if you recall, you can go back to the auth controller where we have our authenticate message pattern
that expects the JWT to be set and runs through the JWT auth guard and the JWT strategy.
And now we will provide the data.
In our case it will be the authentication property.
And now we're going to pass the request dot headers, dot authentication.
So we're pulling the authentication off of the GraphQL request and sending it directly to our auth service
to verify.
Now finally, we will return the user inside of an object outside of this auth context.
So to now actually utilize the auth context, we'll go back to the Gateway module and inside of the
GraphQL module, we can actually add a another property above the gateway property called Server.
And inside of server we can open an object and provide a context parameter which takes in the context
function that we just created.
So we'll pass in that auth context function which will get called every time a GraphQL request is sent
in to our gateway.
So finally we need a way to actually attach the user that gets returned from the auth context onto the
outgoing request that gets sent to our downstream microservices like our reservations service.
So in order to do this, we can add an additional property to the gateway object that will be called
build service.
And this is going to be a function that takes in an options object and we'll go ahead and destructure
the URL from it and then open up a function that will return a new remote GraphQL data source.
And in here we'll open up another options object where we'll pass the URL back in because we want to
maintain the same URL to the downstream GraphQL microservice.
And then importantly, we can attach a middleware function called will send request, which is a function
that gets called every time We are about to send a request to a remote GraphQL data source or downstream
GraphQL service.
So this is another function that has an options object that we can pull the request off of as well as
the context off of that was returned from our auth context.
So now we'll open up a function and what we can do is we can call request dot http headers.
And on these Http headers we will call set to attach a new header.
And in here we're going to add a new header called User.
And we'll check to see if we have a context dot user from our auth context.
Then we want to json.stringify that user.
Otherwise we will just add a null.
So now whenever a request is authenticated and sent to our downstream microservices, we're going to
add this user header which is going to stringify the user that we returned from our auth context and
we'll have this user available later on in our GraphQL microservices.
So now on the other end of this, in our reservations resolver, as we are using the current user decorator,
we'll go to the current user decorator and refactor this to work with GraphQL and our newly refactored
auth context.
So inside of this, get current user by context function, I first want to check to see if the context
dot get type, which is a function that will return the current execution context is equal to Http.
Well then we will just follow the same existing behavior where we switch to Http, get the request and
pass the user on the request object which is populated from our local strategy.
However, if the context dot get type is GraphQL, then what we want to do is extract the user.
So we'll have a new const called user and we can switch the context to GraphQL by calling context dot
get args.
We're going to access the second argument in the context args array.
We'll have access to the incoming request from GraphQL.
We can then get the headers and finally we have the user that we set on those headers.
So if the user does exist on the headers like we set previously in the Gateway module, then we will
return.
Well, if you remember correctly, we just stringified the user.
So now we need to parse it back into an object.
So we'll call Json.parse, parse the user and return it to our correct resolver.
So I'll go ahead and start up all of our services by running Docker compose up.
So now that our services have started up successfully, I can go to my browser and navigate to localhost
3004 slash GraphQL and now we're met with the GraphQL playground UI, which is essentially going to
be the equivalent of using Postman.
But in the GraphQL world now, right now our server cannot get a successful response back, and that's
because we're not supplying any authentication headers to our server, which we know we need to be supplying
in order to be authenticated to access our GraphQL server.
So let's head to Postman where we can launch a request to authenticate with our system.
So I'm going to go ahead and create a new user on the auth microservice by calling localhost 3001 slash
users and post a new account to create this user and then we can launch a request that auth slash login
to log in.
And now we have the JWT sent back to us.
I'll go ahead and copy this.
And now I can go back to the GraphQL playground and on the bottom here I've clicked on Http headers
to open up this headers window and we will create an object here and we'll supply our first header by
providing an open string and setting the header authentication.
And if you remember, this corresponds to the header that we're pulling off in our auth context.
We're pulling it off of request dot headers, dot authentication.
So that's going to be the same set of headers that we're providing here.
Let's go ahead and open up colon quotes and now paste in the JWT from that postman call.
So now we can click on the right tab over here schema to view our raw GraphQL schema, which Nestjs
generated for us using the code that we decorated using the input type, object type and field decorators.
We have the entire GraphQL schema that we can view here, which includes all of our mutations, queries
and arguments.
However, to view this in a more readable format, we can click on the docs window and now we can see
a nice view of all of the queries and mutations we have available to us which are coming from our downstream
GraphQL services we defined in the Gateway module.
So right now this is just a reservation service, GraphQL Server.
We have the query to query all reservations and a single reservation to get back the reservation document.
We can see the type that comes back.
So this is going to be the reservation document which we decorated with the field decorators earlier.
So all of these properties that we decorated have been added to these docs here and we can see the mutations
as well to create a new reservation.
And we can even see the arguments that are required for this call and our Create Reservation DTO So
this is very useful because it generates all of this great documentation for us.
Well, without having to do anything besides merely decorating our code properly.
So let's go ahead and actually try out these mutations and queries to make sure everything's working
properly.
So to do this, I'm going to execute a mutation by typing a mutation open curly brackets.
And now we have access to the mutation that we called Create Reservation.
And now if we open up parentheses, we can automatically have the input expects here to be supplied
so we can supply a colon and then open up another object where we supply the create reservation input.
So I've gone ahead and copied an existing request from Postman and pasted it into our options object
here to supply all of the parameters that the Create Reservation input expects, including our start
date, end date and charge object, which contains all of the necessary details to create a reservation.
So now what we're going to do is we're going to open up another curly brackets at the end.
End here.
And this is where we actually tell GraphQL which properties we want to pull back from this call.
And this is another one of the great benefits about GraphQL is that we don't pull back all of the data
by default over the wire.
We're able to choose which properties we want to send back and limit the amount of data that gets transferred
to make things a bit more efficient.
However, in our case, I want to pull back every property.
So let's go ahead and specify all of these properties, including the start date, end date, invoice
ID.
We can pull back the user ID and finally the timestamp as well as the underscore ID.
So now if we execute this request to create a reservation, we can see the request succeeded and we
get this data sent back to us that we requested in the mutation.
So now let's go ahead and open up another tab up here to actually query this reservation that we just
created.
Let's use the query keyword and now we'll call reservations to pull back all reservations.
And now we can just simply paste in all of these properties we've already asked to get back from the
mutation.
We'll paste in to this object and we can get back all of the reservations and we can see the one we
just created.
Now, if we want to be more selective and query by ID, we'll change the query to just reservation.
And now it's complaining because it expects that ID on the reservation.
So let's go ahead and supply the ID now.
And now if we execute this, we get back the single reservation that we just created.