Auth in Nest.js and Angular
This series will we split up into two parts:
- Part 1: back-end with Nest.js, Google OAuth and JWTs (this post)
- Part 2: front-end with Angular, Material and NgRx
Back-end: Nest, Google OAuth and JWTs
We will start by building our back-end. Lets install the Nest.js CLI and start a new project:
$ npm i -g @nestjs/cli$ mkdir tutorial && cd tutorial$ nest new back-end && cd back-end
Press enter a couple of times and wait for the CLI to scaffold your app. You should now have a directory called back-end
in which you can find your generated Nest.js app. The directory back-end/src
contains the current code of our application. The file main.ts
bootstraps the application and should look something like this:
We can run our application by running the following command, which will automatically re-build our application when it detects changes:
$ npm run start:dev
Check whether your application works by executing the following command in another terminal:
Okay, we are up and running! If you are not yet familiar with the basic concepts of Nest.js (Modules, Services, Controllers, DI, etc.) you can checkout the docs here:
We will start by creating an auth
module which will contain our services, controllers and interfaces for handling auth in our application. Let's use the Nest.js CLI to generate an auth
module, controller and service:
$ nest generate module auth$ nest generate controller auth$ nest generate service auth/auth
We should now have an auth
directory with a module, controller and service. Our app will handle authorization by using Google as a single sign on provider. Our authorization and authentication flow will be as follows:
- Front-end contacts back-end, which redirects theuser to Google login page
- Users logs in and Google calls our back-end with user information
- Back-end processes user information (registration / login + JWT generation)
- Back-end redirects user to front-end with JWT as a parameter
- Front-end will register JWT and attach it to each request made to our back-end
Google OAuth2 Strategy using @nestjs/passport
Let's get started on our Google login flow. We will need to install some dependencies for Nest.js and Passport.js:
$ npm i --save @nestjs/passport passport passport-google-oauth20
Create a google.strategy.ts
file in the auth
folder with the following content:
We created a class GoogleStrategy
which extends the PassportStrategy
from @nestjs/passport
using the passport-google-oauth20 Strategy
. The second argument to the PassportStrategy
is the name of the strategy, in this case google
. The validate
function will handle a successful login on the Google login page. We simply log the profile of the user that Google sends us. Later on we will come back and add some logic for registering users and generating a real JWT, instead of our placeholder.
Import the GoogleStrategy
as a provider in the AuthModule
:
We need to configure our OAuth2 strategy with credentials provided by Google. These credentials include the clientID
and clientSecret
. Let's go to https://console.developers.google.com and register our application. Create a new project, select it and enable the Google+ API by clicking on 'enable APIs and services' and searching for the Google+ API. Then go to 'credentials' and select the 'OAuth client ID' option when trying to create credentials.
Choose 'Web application' as application type and continue. Add http://localhost:3000
under the Authorized Javascript origins and add under Authorized redirect URIs. Google will redirect the user information to our callback URI when a user successfully logs in. Save and replace the clientID
and clientSecret
in your google.strategy.ts
file by the tokens generated by Google. Your config should look like the one below.
Important: do not leave your plain text google credentials in your application. Rather, use environment variables. A good way to do this in Nest.js is by creating a configService
as described in the docs.
We now have a valid Passport strategy in Nest.js. However, we cannot use it yet, because we do not have any endpoints handling the initiation of the Google login flow and the callback containing the user information. Let's go to our auth.controller.ts
and make the appropriate changes:
Pfew, lots of decorators. So what did we do here? We created two REST endpoints:
@Get('google')
: when we gohttp://localhost:3000/auth/google
in our browser the login flow will start. We protected this route using the@UseGuards
decorator in combination with theAuthGuard
from@nestjs/passport
. The argument toAuthGuard
is the same name as we used in ourGoogleStrategy
class. TheAuthGuard
will take care of the request and make sure our Passport strategy, which we created in theGoogleStrategy
class, gets activated.@Get('google/callback')
: after the user has logged in, google will send the user information to this endpoint (we provided this URI to google when we registered our application!). We also protect this route using the same decorators as with the@Get('google')
route, so our Passport strategy gets activated. When this endpoint gets called thevalidate
function in ourGoogleStrategy
class gets activated and the flow is completed.
The body of our googleLoginCallback
function might look a bit weird at first: why does the req
object all of a sudden have a user
property with a JWT? Well, in our GoogleStrategy
class we have a function validate
which gets called when Google hits our http://localhost:3000/auth/google/callback
endpoint. In this function we have a callback function done
which we call as done(null, user)
. The first argument is an error (in this case null
) and the second argument is the user
object we created, containing our placeholder JWT (we will take care of this later). Passport attaches object given to the callback function as a user
property to the req
and that is why we can access it in our googleLoginCallback
function!
Last but not least, we redirect the request to a front-end route http://localhost:4200/login/succes/<JWT>
when there is JWT. We will handle this on the front-end later on. We there is no JWT we redirect to a failure route. This way, our front-end can complete the user login and register the JWT token, whilst our back-end safely handles all the sensitive user information it received from Google.
Pfew! That was a lot of work and reading. Let's see if we can test our Google login flow! Open your browser and navigate to: http://localhost:3000/auth/google
. You should now be able to login with your Google account. When you login, you will see your profile printed in your terminal and you will get redirected to localhost:4200/login/success/placeholderJWT
if the login is successful, awesome!
How about that placeholderJWT
?
Right… let's do something about that! Instead of returning a placeholder JWT, we want to create a real JWT. Let's install some dependecies for dealing with JWTs:
$ npm i --save jsonwebtoken
We will implement the logic for this in our generated auth.service.ts
file containing our AuthService
. Open the file and make the necessary changes:
We created a function called validateOAuthLogin
, which will return a JWT token.
Sidenote: you should put some registration logic here, so you can store your users in a database. This is not in the scope of this post, so we will not handle it here. However, the comments might give you an idea on how to implement such functionality.
Our function accepts a thirdPartyId
(i.e. Google userId) and a provider enum. By implementing the function this way, it stays reusable and can thus be used for other social login providers like Github (simply repeat the same process as for the GoogleStrategy
but now use the Github specific passport strategy, Github credentials and Github endpoints)
We use the sign
function from jsonwebtoken
to sign our object containing the thirdPartyId
and the provider
using our JWT_SECRET_KEY
and an options object. The options object describes how long the JWT is valid. In our case it is valid for 3600 seconds or one hour. More configuration options can be found in the jsonwebtoken
docs (https://github.com/auth0/node-jsonwebtoken).
Important: you should replace the value of JWT_SECRET_KEY
with your own generated secret key. You can generate a JWT by using the following command:
$ node -e "console.log(require('crypto').randomBytes(256).toString('base64'));"$
vwts1k+tsUuMYh1ZIGQ5eeWu/DjlHy2xlNXsBC6dzyFXRhVrC/d2R4SbhLhsbiWlJHqTEHPUA9N7l+UfGziEYixc0xqif5PHY+d7DojbebbFws/mik07eJf6MkE+SAC1jbQm2EY6C6vhIdcXbIDLwnjL3ePzyW4Itu68N4nbugPRkQO/5T0N27TDCBNQG8vDkh0609iZFU3bw5609Egu7H2XwiR6sqPv7xj1j1Qw8TipLoQ/XSuzmArgsWABQu6u6X/KKh6dTSTDWroCQNwx1Y1870uwNBZKWiBYAFhCWFdqEX6uWZyp4XZZ0lgYWXK67/4qkfgSDal7wbHihJ4lNw==
Important: do not leave your plain text JWT_SECRET_KEY
in your application. Rather, use environment variables. A good way to do this in Nest.js is by creating a configService
as described in the docs.
Great! Let's now change our validate
function in the google.strategy.ts
file to make use of our validateOAuthLogin
function by injecting our AuthService
into the constructor of theGoogleStrategy
class and calling it in the validate
function.
Great, we now return a real JWT if a user signs in with their Google account. Try logging in again. When you are now redirected to http://localhost:4200/login/succes/<JWT>
you should see an actual JWT instead of placeholderJWT
. A JWT is simply a Base64 encoded string. You can copy the JWT and decode it on the following website: https://www.base64decode.org/. Paste your JWT and click on decode. You should see a JSON object with the following properties:
thirdPartyId
: the id of your Google accountprovider
: the provider which you used to log in, in our case 'google'.iat
: the epoch time of when our JWT was issued by our back-endexp
: the epoch time of when our JWT expires
Important: JWTs are just simple Base64 encoded strings. Do not put any sensitive information about the user in the JWT. This information would be easily accessible to anyone managing to get hold of the JWT.
Another part done! On to the next.
Protecting our API using JWTs
Great, we can now login using Google and generate JWTs. We can use the JWT to authenticate ourselves with our back-end, when we want access to protected resources. We are now going to implement the Passport.js JWT strategy, so we can protect our API. Let's install the Passport strategy dependency for JWTs:
$ npm i --save passport-jwt
Now, create the JWT strategy file jwt.strategy.ts
in theauth
folder with the following content:
The JwtStrategy
class is very similar to our GoogleStrategy
class. However, we now extend the PassportStrategy
using the Strategy
imported from the passport-jwt
package and we provide 'jwt' as a name for our strategy. In the constructor's super call we instruct Passport to extract the JWT as a Bearer token from the request made to our API and use the secret provided. The secret we provide to secretOrKey
should be the same as the one we used in our AuthService
. We call the done
callback with the decoded JWT payload. We do not need to decode this ourselves, Passport handles this for us. This payload object is, just as it was the case in the GoogleStrategy
, attached to the request under the user property (req.user
).
Wait, but what if the JWT provided to our back-end would be invalid (i.e. not signed by our secret key) or expired? Well, Passport will also handle this for us and will send a response with an HTTP status code of 401
, indicating the user is unauthorized and thus the JWT being invalid.
Sidenote: we could also implement some logic to validate the claims of our JWT token here. For example, the token could contain a property describing the roles of a user. It would be necessary to check whether the user still has the roles the JWT claims to have. However, this is outside the scope of this post. The comments provide an idea on how this could be implemented.
Let's now register our JwtStrategy
in the AuthModule
:
Great, let's move on to creating an API endpoint and protecting it with our JWT strategy! Let's go back to our auth.controller.ts
file and add an extra endpoint:
We added an endpoint /auth/protected
and used the @UseGuards
decorator with the AuthGuard
using the 'jwt' strategy. Our route should now be protected. Let's test this with the following command:
We get a HTTP status code 401
, indicating we unauthorized. Now let's get a JWT from our back-end by logging in by going to http://localhost:3000/auth/google.
Copy the JWT from the redirect URL in your browser. Now execute the following command, but replace <YOUR_JWT_HERE>
with the JWT you copied:
$ curl -i http://localhost:3000/auth/protected -H "Authorization: Bearer <YOUR_JWT_HERE>"$
HTTP/1.1 200 OKX-Powered-By: ExpressContent-Type: text/html; charset=utf-8Content-Length: 18ETag: W/"12-PGbHJZOgiw3wT+Qbl2IshJem8RE"Date: Fri, 05 Oct 2018 21:53:38 GMTConnection: keep-alive
JWT is working!
Yes, we can now access our protected endpoint and we get the expected response: JWT is working!
.
Summary
Wow, that was a lot of work. This is it for the back-end part though! We are now able to login using Google. Our back-end then receives the user information from Google and will redirect us to our front-end (which we are gonna setup next) with our own JWT. We protected our API using the JWT strategy and access it only if we attach a valid JWT. The authorization and authentication back-end flow is done. Good job!
Next part
In the next part we will take a look at building an Angular front-end with Angular Material for the UI and NgRx for state management. The front-end will make use of the authorization and authentication flow we just built. Also, instead of navigating to our back-end manually to start the login flow, we will provide a nice google button. I will be working on part two in the coming week.