Okay.
So we have the ability to apply authentication to our routes directly inside of the auth app.
Right now, however, I want to be able to add authentication to our reservations controller so that
any of these routes here will be protected by a JWT auth guard and the user must be authenticated to
access these routes.
So in order to do this, we need to have a way to connect our microservices together so that the reservations
can talk to auth and authenticate a user.
Nestjs offers this out of the box with a number of different transport options to support networking
between our microservices.
We're going to use a standard TCP based transport layer to be able to connect our microservices together.
So let's go ahead and start wiring up our auth service to be able to talk as a microservice.
Firstly, we're going to go back into the terminal and we're going to go ahead and install nestjs,
slash microservices so we can go ahead and start our server back up by running Docker, compose up here
and now inside of our auth service, we're going to convert this into a hybrid application, one that
listens for both incoming Http connections in our controllers as well as over our microservice layer
through TCP.
So in order to accomplish this in our main.ts, we're going to go ahead and call app dot connect microservice,
which will allow us to specify an options object here where we can specify the transport that we want.
So like I said before, there are a number of different transport options available, including Redis,
Rabbitmq and Kafka, which I have covered in past videos and I may add to this course as well.
However, for now we're going to make use of just a standard TCP protocol so we can set this with the
transport from Nestjs microservices, and this is all the different transport layers.
As I mentioned, we're going to choose TCP, which is the default out of the box.
So now that we've connected a microservice here, as well as calling App.listen on our port for Http
connections, we're going to call await app dot, start all microservices to start our microservice
up over this TCP transport layer and listen for incoming events and requests.
So if we go back in our terminal here, we should see the auth service start back up in addition to
the Nest app starting up successfully here, we can also see that the nest microservice started successfully
here in our logger for sleep sleeper auth.
Now we're actually ready to talk to this auth microservice.
So now that our auth service is listening for incoming TCP connections in our microservice architecture,
I want to go ahead and create a new common auth guard that all of our services can utilize to check
to see if a JWT passed to them is valid.
So what it's going to do in that auth guard is it's going to reach out and make a call to our auth service
through TCP and determine if the provided JWT is indeed valid.
If it's valid, we will return the user.
I want to actually put this off guard into our Libs common folder because I want to use it in all of
our different microservices.
It'll be the same guard, the same way to authenticate for all Http routes outside of the auth service.
So let's go ahead and create a new auth folder.
I'll go ahead and create firstly an index.ts and make sure that we export everything from this off folder.
And now in here I'm going to go ahead and create a new JWT auth guard.
So this is going to be an injectable class here and we'll export the class JWT auth guard, which is
going to implement the can activate interface from Nestjs Common.
Then I'll go inside of the Index.ts here and export everything from the auth guard that we just created.
So we're complaining that because we're not implementing the can activate method here.
So let's go ahead and actually implement this.
So remember this JWT auth guard is going to be sitting in front of any of our public facing API routes.
It's going to expect to be passed a JWT cookie in order to authenticate past it.
So we're going to create a new variable here called JWT and set it equal to context.
Switch to Http, get request to get us the current in-flight request object for this route.
And then inside of there, we're going to look for the cookies object, which the cookie parser library
is responsible for creating and populating.
So any service that actually implements or uses this off guard will have to use the cookie parser library
to make sure we get the authentication cookie properly set on here.
So we'll pull that authentication cookie off of this object so we can do a quick check here to see if
we don't have a JWT provided by the user, we will simply return false returning a boolean false.
From this guard here will not allow the current request to proceed.
So now we need to actually reach out to our auth microservice to determine if this JWT is valid and
get the associated user.
So we're going to assume that any any service that uses this JWT auth guard is also going to have a
client proxy available to inject for this auth service, which is how we talk to other microservices
in this nestjs architecture.
So let's see how this actually looks like.
We use the inject keyword here to actually be able to inject this client proxy.
And right now we don't actually have this injection token defined for the auth service yet.
So let's go ahead and do that in the common folder.
We'll go ahead and create a new constants directory.
And inside here we'll create a new file called Services Dot TS, and we'll just simply export a const
here called auth service, which is just going to be called Auth.
So now that we have this constant defined, let's actually use it in here.
We'll import the auth service and this is going to be a private read only auth client of type client
proxy from Nestjs microservices, which is the object that will allow us to communicate to our other
microservices via the provided transport layer.
So now that we actually have the auth client here that we can use to talk to the auth service, we need
to actually set up a route in our authservice that is set up to be able to receive RPC calls.
And in order to do that we'll go into our auth app, into the auth dot controller here and we're going
to add a new route in this controller and it's going to use the message pattern decorator from Nestjs
microservices, which is going to allow us to accept incoming RPC calls on our chosen transport layer.
So this is going to be very similar to our Http call here for post where we provide the route name.
In this case, we're going to provide the message pattern name.
In this case, I'm going to call it authenticate.
So this is what we're going to use to actually authenticate our JWT and I'll call this method async
authenticate.
So in this authenticate route, all I want to do is actually just run it through our existing JWT auth
guard.
We'll have to modify it a bit, but it'll use the same logic.
We want to check in incoming JWT, verify that it's correct and return a user that's associated with
it, which is all logic we've already set up in our JWT strategy over here where we are currently extracting
the JWT from the request object here.
So let's go ahead and apply this auth guard.
The JWT auth, guard, guard still work just the same in microservices, so we can be sure that our
request will still be routed through the JWT auth guard.
So let's go inside our JWT auth guard which of course we know it actually implements the strategy here.
Right now we are pulling the JWT off of the request header in the cookies object here.
However, when the JWT is coming in from our RPC call in our JWT off guard, it's not going to be inside
of a cookies object.
It's just going to be under the straight request object.
So we can put in a bit of logic here to say if there is no request cookie, we can then check to see
if the authentication key actually exists on the request object itself here.
And we're also going to change the type here to be any because this can be a request from Express or
from an RPC call.
Okay.
So now that we have set up a message pattern for this authentication flow, let's go back into our auth
guard and we're actually going to make this call using the auth client.
So what we're going to do here is we're going to call this dot auth client dot send, and this is going
to send a message to the auth service that matches the current pattern.
And right now, of course we know the pattern we want to target is the authenticate message pattern
that we just created.
And next we have the data object, which is going to be the data that we send in this call and we know
we need to add the authentication key here.
And this value here is going to be the JWT that we've already extracted from the incoming cookies body.
So we're actually going to go ahead and return this observable altogether, which is a valid return
type here of observable boolean, and we're going to pipe in additional operators onto this observable.
The first operator is going to be the tap operator, which of course we are importing from Rxjs, which
is going to allow us to execute a side effect on the incoming response And the incoming response from
the auth service will eventually be the user itself that is associated with this JWT.
So what I want to do is I want to.
Take that user and add it to the current request object.
So to do this we are going to call context dot switch to Http get request here and then we are going
to set the user object on the request equal to the response.
We get back from the auth service, which will be the user itself.
We'll use the map operator from the Rxjs package and return.
True.
If we have a successful response back from the auth microservice, meaning we are authenticated, we
want to return true from a can activate which will allow the request to proceed and make sure we're
authenticated.
We're ready to integrate our newly created common JWT auth guard into our reservations routes to lock
them down to only authenticated users.
So we'll open up our reservations controller and start with a post route right here.
We will use the use guard's decorator here and import the JWT auth guard and we can see it's directing
us to the common folder here and we'll change the path directly by targeting app slash common.
So now we're getting an error from the reservations app that we can't resolve the dependencies of the
auth guard, which makes total sense because we haven't actually provided the auth service injection
token here in the reservation service where we're trying to use it.
So we'll go ahead and take care of that now in the Reservations module and to actually register this
service and provide this injection token, we're going to use the clients module dot register call here
where we're going to pass in the injection token.
In this case, we know, of course, it's the auth service which will import up top from App Common
and we can add to this existing import here.
So we'll import the auth service and we can see we're not actually exporting it right now.
So let's go ahead and change that out by adding a index.ts in our constants directory here and then
we'll just export everything from the services file now and in this route in Index.ts, we'll of course
need to export everything from the constants directory and now we should be able to import properly.
So we have this auth service for providing and we need to specify the transport layer, which of course
we know is going to be at the TCP layer.
So now if we go back to the terminal, we can see the reservation service is finally starting up, just
like we've done in the auth service where in the Main.ts we're using cookie parser here to parse our
incoming cookies and add them to the incoming request object.
We'll do the same thing in our main.ts for the Reservations app here and I will similarly copy over
the import itself for cookie parser.
Okay, so back in Postman.
Now if we send a request to localhost 3001 slash auth slash login with our email and password for a
user we've already created and we send this post request off.
We are authenticated and of course we get this JWT cookie set.
So now we should be able to send a request to localhost 3000 slash reservations and post a new reservation.
So let's go ahead and send a sample request and you can see immediately we're getting a status code
500 internal server error.
We can actually see in the logs here for our Reservations app that we're having an error during our
request.
And the error here is that the TCP connection is actually closed and was never actually created between
our Reservations app and our auth service.
So there's some issue communicating between the two services.
And the issue that we're having right now is in our Main.ts for our auth service.
We haven't specified the host and port that we want to be listening on for our TCP microservice here.
So I'm going to have a new options object here where we do just that.
Our hosts property here is going to specify the 0.0.0.0 IP address, which tells the microservice to
bind to all interfaces on the host.
And then for the port here, we want to actually extract this from the environment.
So let's get the config service out of the app up here before we connect the microservice for the port
here, we'll call config service dot get and we'll call this environment variable TCP port.
And I'll also change the port variable for our Http server and call this Http port just so we're a bit
more descriptive about these ports.
And then we're going to go into the auth module and change our validation.
We'll say that this port should now be.
Port, which is a number as well as adding a new one here.
So we can add the TCP port, which will also be a number.
So finally we'll have to go to our EMF here for the auth service and change the Http port to be 3001.
And then I'll add a new environment variable, the TCP port, and I'll set this to 3002.
So now in the main.ts we are pulling the TCP port for our port and we're binding this microservice to
all public interfaces on our host so that we can listen for incoming requests.
So now that we've set the options for the auth microservice itself, we need to go into the reservations
module and specify these options in a new options object here for the auth service, where we will of
course specify the host and port.
Now we want to be able to use a config service to get the host and port in our client module registration
here.
So to do this we'll actually copy the existing object we have and instead of calling register on clients
module, we can also call the register async version here that's going to allow us to use a use factory.
So to actually configure this register async, we're going to provide an array here where each entry
in the array is a different client.
So we'll go ahead and provide a single entry here where we have the name property, which we're going
to keep as the auth service.
So this is still the injection token.
And then we have the use factory here, which is going to allow us to inject the config service from
Nestjs config where we're going to return the original options object, where of course we have the
transport layer we know will be transport dot TCP and then we have the options object here where we
will specify the host which we're going to pull off the config service dot, get auth host and then
we'll have the port as well which will be set to config service dot get auth port.
So we now need to go into our env for the reservations app and provide these environment variables.
So the auth host is going to be actually defined in the Docker compose here in Docker, these services
can talk to each other by the actual names of the services we defined here.
So the host name for auth will just be auth and the host name for reservations is just reservations
running in Docker.
So we will set the auth host here to just be auth.
And then we know of course that the auth port here will be 3002, which is corresponds to the TCP port
we defined in the auth service.
So we'll set this to 3002.
So finally make sure that we actually provide the inject property here or we go ahead and inject the
config service as well.
Okay.
So back in Postman, I'll go ahead and launch another request localhost 3001 all slash login with a
valid user so that I have a JWT cookie set and then we'll go back to localhost 3000 slash reservations
to create a reservation and we get an internal server error.
So if we take a look now you can see we're erroring out because we have an rxjs error.
No elements in sequence, which really means that we're just not returning anything from our observable,
which we know makes sense because right now inside of our auth controller for the message pattern,
we're not actually returning anything right now.
So let's go ahead and firstly take a look at the strategy itself When we're sending this request because
I want to see that the JWT auth guard here is actually sending the JWT correctly.
So let's go ahead and see that by going to the JWT strategy and instead of returning the straight authentication
here, let's open this up as a function body here where we are going to return the original statement.
But then I just want to log out the incoming request so that we can see it inside of this JWT strategy.
So now if I send a request, we still get this error.
However, if we go up in the auth service, we can now see that we're actually logging out the request
in the strategy.
And you can see importantly, we have this authentication property set on the incoming request or in
this case data object.
And this matches exactly an auth guard, the object that we're sending to this message pattern controller.
So the JWT looks correct.
Let's go ahead and back in the strategy.
We'll undo this here.
And and so I've gone ahead and just returned the straight authentication keyword here.
Now that we know we're getting the JWT correctly, let's go into the auth controller and we're going
to use the ad payload decorator here from Nestjs Microservices.
Which will extract the current payload for this message pattern, which is the same exact request object
here that the JWT strategy receives.
So we can actually get this object in the route controller with the payload decorator.
We'll call this data of type any and let's just console dot log the data now.
So after we've run through the Jdbc strategy and we've set the user on the data object or the request
object here, we should now see that user in this payload object.
So let's go ahead and actually verify that if we send the postman call again, we'll see in the logs.
Now if we scroll up to the auth service where we can actually see the data object being outputted.
And of course now we see the user object which the JWT strategy added for us after it authenticated
the JWT and got the user from the user service.
So now we can simply return the data dot user back to the caller here.
So now if we send off a request, we can see we have finally succeeded in sending a request, although
now it's just malformed because we don't have the correct Json body.
We are indeed passing our JWT auth guard and we are correctly authenticating against this route.
So one thing I want to do before we wrap up in the reservations controller, now that we are actually
authenticating our Create Reservation route here, I want to extract the current user on the request
object, which if you remember, we actually set here in the JWT auth guard.
So if you remember, we've already actually implemented this functionality in the auth service.
The current user decorator here where we pull the current user off of the execution context and return
it back to the params decorator.
I'm going to go ahead and export this decorator in our common folder so we can use it in all of our
services.
So let's go ahead and create a new Decorators folder here in our common directory.
I'll follow our similar pattern of creating Index.ts and in our route index, make sure we go ahead
and export everything from decorators.
Now we'll go ahead and copy the current user decorator from the auth project and throw it in the decorators
folder here and we will just make sure we export everything from the current user decorator now.
So make sure you go back to the auth controller and make sure that we are importing the current user
from app slash common here and now.
I'll also go into the reservations controller where we can also use this current user decorator from
app slash common to get the user.
So we want to be able to define a type for this user and, and in particular this user as defined as
a DTO or when it's returned from a network call like in this case from our JWT auth guard.
So let's go into our Libs common folder and we'll create a new directory in the source directory here
called DTO and we will create a new user dot dto ts, which will be an interface that represents a user
data transfer object.
So looking at the user dot schema here, we know that we have an email password and underscore ID.
However, as a DTO, a data transfer object, we know that the object ID will of course be a string
and not an object ID when it's sent over the wire.
So let's go ahead and add these properties.
We have the underscore ID email here as a type string and a password of type string.
So now that we have this, we'll add an index dot ts to our DTO folder will export everything from the
user DTO and our route Index.ts will export everything from the DTO folder.
Additionally, we can go into our Jdbc auth guard and give a type as to user DTO for the send call here,
which will give us some better type inference and actually specify the response here as the user DTO
when we set it on the request object, additionally we can go into the reservations controller now and
actually define this as a user.
DTO And now what I want to do is I want to go into the create call in our reservation service and instead
of hard coding this user ID here, we're going to accept it as an additional user ID parameter which
will come off of the JWT from our off guard.
So now that we're passing the user ID here, we'll go in and pass the user.id as here as well.
And we'll go ahead and set this equal to underscore user and await it just so we can log out the current
user and actually see it, then return it back to the.
We can also see that we're missing an async call in all of these routes.
So let's go ahead and add that and fix this to make sure that we are marking these routes as asynchronous
here.
So back in Postman here, I've updated the body to our reservations call here to include the payload
expected which is the start date, end date, place ID and invoice ID.
So I'll go ahead and send this off.
You can see we have a 201 created and we can see in the logs now we can actually see the user that is
logged out in this route handler, which of course we know is this log here.
So we're correctly extracting the current user after we set it here in the JWT auth guard, which is
excellent because now we have the ability to apply authentication to any route in our microservice architecture.
I'll go ahead and clean this up here by making sure we return just the straight create call here and
then I'm going to go ahead and apply the same JWT auth guard to the remaining of our routes to make
sure that they're all protected by a JWT auth guard.
Now we can call get on our reservations route here and see our two reservations right now.