#nestjs controller explained
Explore tagged Tumblr posts
Video
youtube
Nestjs Controller Tutorial with Example for JavaScript Developers | #nes... Full Video Link - https://youtu.be/Ui4PXXbY1P8 Check out this new video on the CodeOneDigest YouTube channel! Learn nestjs controller with example. Learn how to create controller route, access request object, status code etc. #video #nestjs #controller #nodejs #javascript #codeonedigest@java @awscloud @AWSCloudIndia @YouTube @codeonedigest #typescript #javascript #nestjs #javascript #node #nestjs #nestjstutorial #javascript #nestjsmicroservices #nestjscontroller #nestjscontrollertest #nestjscontroller #nestjscreatecontroller #nestjsfulltutorial #nestjsfullcourse #nestjsproject #nestjsrouting #nestjstipsandtricks #nestjsroutingincontroller #nestjsprojecttutorial #nestjsprojectfromscratch #nestjsexampleproject #nestjstesting #nestjsprojectsetup #nestjscontrollertest #nestjscontrollerproject #nestjscontrollerrouting
#youtube#nestjs#nestjs controller#nestjs routing#nestjs controller routing#nestjs route#nestjs controller explained#nestjs tutorial
0 notes
Link
Master NestJS – The JavaScript Node.js Framework
Learn to develop and test enterprise-grade Node.js applications in TypeScript. Learn modern workflows using Docker.
What you’ll learn
Master NestJS – The JavaScript Node.js Framework Developing robust REST APIs Enterprise-Grade Node programming using Nest Testing with Unit & End to End Tests Robust Development Workflow using Docker Using modern database abstraction (TypeORM) Description NestJS is the best way to enter the world of JavaScript/TypeScript Node.js programming. It’s an easy-to-learn and powerful framework. This course will guide you from the very basics into more advanced concepts, including:
Routing and controllers
Databases including TypeORM (Repository, Query Builder, Relations)
Using Docker in your local development workflow
Data validation and serialization
All about NestJS modules, Dependency Injection, and Providers
Configuring, logging, error handling
Authentication including Passport.js, Bcrypt
JSON Web Tokens (JWT) tokens explained, generation and usage
Authorization (making sure the user has privileges)
Using Postman (including collections, environments, automating Postman)
Unit testing
End to End testing (including connecting to a database)
The course comes with full source code included and available on GitHub at all times. Including a separate branch for every lecture with code changes. I’ve made sure everything is as clear and simple as possible, so there are a lot of diagrams and visual aids included (and available for download too!). Join the community in the Q&A forums and on our Discord channel – talk to other students, share your progress, questions and insights! I made extra effort to organize the topics in a way that would make you enjoy the process of learning. The course is short and to the point but covers plenty of topics in surprising detail! get more courses from the course catalog.
1 note
·
View note
Video
youtube
Nestjs Provider Tutorial with Example for JavaScript Developers | Depend... Full Video Link - https://youtu.be/Ld2q1ilsilo Check out this new video on the CodeOneDigest YouTube channel! Learn nestjs provider with example. Learn how to create provider, what is dependency injection & IOC #providers #nestjs #ioc #dependencyinjection #nodejs #javascript #codeonedigest@java @awscloud @AWSCloudIndia @YouTube @codeonedigest #typescript #javascript #nestjs nestjs,javascript,nestjs tutorial,nest,nestjs microservices,nestjs providers,nestjs dynamic provider,nestjs project,nest js full tutorial,nest js full course,nestjs framework,nest js for beginners,nest js provider tutorial,nestjs provider tutorial,nestjs example project,next js explained,nest js tutorial playlist,nestjs controller,nestjs modules,nestjs module,dependency injection,dependency injection nestjs,inversion of control,dependency injection nest
1 note
·
View note
Text
Adding JWT Authentication to an Ionic Application with MongoDB and NestJS
Many Ionic applications will require the concept of an authenticated user who is authorised to perform certain actions - perhaps only authenticated users may access your application at all, or perhaps you want to restrict certain types of functionality in your application to certain types of users.
There are many different options that can be used to add this functionality to your application, including services like Firebase and Auth0 which do most of the heavy lifting for you and provide a simple API for authenticating users. However, you can also create your own authentication/authorisation functionality, which may be a more attractive option if you are taking a self-hosted approach to your backend.
In this tutorial, we are going to cover how to create our own authentication system for an Ionic application with MongoDB (to store users other data) and NestJS (to handle HTTP requests to the backend). The basic flow will work like this:
A user will create an account (if they do not have one already)
The account will be stored in the MongoDB database (if it has not been already)
The user will supply their email and password to sign in to the application
If the sign in is successful, the user will be supplied with a JWT that identifies them
Requests to any restricted routes will require that the JWT is sent along with the request in order to access it
I will not be covering what JSON Web Tokens (JWT) are in this tutorial. If you are not already familiar with the concept, I would recommend reading this article. In short, a JWT supplied to a user cannot be modified by the user (without breaking the signature), and so if we supply a JWT to the user that says they are Natalie, we can trust that is true since we know the user couldn’t have just edited it themselves on the client-side. If the someone were to try to modify a JWT (e.g. changing Natalie to Josh) then when the JWT is checked on the server we would be able to tell that it was tampered with (and thus reject the request). This concept means we can check a users authorisation without needing their password after they have signed in initially.
A critical point about a JWT is that they cannot be modified not that they can’t be read. A JWT is encoded and that may give the illusion that you could store sensitive data in the JWT, but you should definitely never do this as a JWT can be easily decoded by anybody. A JWT is good for storing information like a user_id, an email, or a username, but never something sensitive like a password. Another important thing to keep in mind about a JWT is that anybody who has it has the ability to be authorised as the “true” owner of the JWT (e.g. if someone managed to steal a JWT from someone, they may be able to perform actions that they should not be authorised to do). We will be taking a very simple approach in this tutorial, but keep in mind that other security measures are often used in conjunction with a JWT.
IMPORTANT: This tutorial is for learning purposes only. It has not been rigorously tested and it is not a plug-and-play solution. Authentication/authorisation and security in general, are important and complex topics. If you are creating anything where security is important, you should always engage the help of an expert with relevant knowledge.
Before We Get Started
Although this tutorial is completely standalone, it continues on from the concepts that we have covered in the previous NestJS tutorials:
An Introduction to NestJS for Ionic Developers
Using Providers and HTTP Requests in a NestJS Backend
Sending Data with POST Requests to a NestJS Backend
Using MongoDB with Ionic and NestJS
If you have not already read these, or you are not already familiar with the basic NestJS concepts, I would recommend that you read those first as I won’t be explaining those concepts in this tutorial.
In order to work with MongoDB on your machine, you will need to have it installed. If you do not already have MongoDB installed on your machine, you can find information on how to do that here. I also released a video recently that covers installing MongoDB on macOS: Installing MongoDB with Homebrew on macOS.
Once you have installed MongoDB, you will need to make sure to open a separate terminal window and run the following command:
mongod
This will start the MongoDB daemon, meaning that the database will be running in the background on your computer and you will be able to interact with it.
1. Create a New NestJS Application
We will start by creating a fresh new NestJS application, which we can do with the following command:
nest new nest-jwt-auth
We are also going to take care of installing all of the dependencies we require as well.
npm install --save @nestjs/mongoose mongoose
This will install mongoose and the associated NestJS package that we will use to interact with our MongoDB database.
npm install --save bcrypt
We will be using bcrypt to hash the user passwords that we will be storing in the database.
npm install --save passport npm install --save passport-jwt npm install --save @nestjs/jwt npm install --save @nestjs/passport
We will be using the Passport library to implement authentication “strategies” - this helps us define the process that will be used to determine whether a user is authorised to access certain routes or not. We will be implementing a JWT strategy, so we also require the JWT packages.
Before we move on, we are also going to enable CORS which will allow us to make cross-domain requests to our NestJS server.
Modify src/main.ts to reflect the following:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); await app.listen(3000); } bootstrap();
2. Set up the MongoDB Connection
In order to interact with our MongoDB database, we will need to set up the connection in the root module of our NestJS application.
Modify src/app.module.ts to reflect the following:
import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [ MongooseModule.forRoot('mongodb://localhost/authexample') ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
In this particular example, we will be using a database called authexample but you can name this whatever you like (there is no need to “create” the database beforehand).
3. Create the Users Module
Now that we have all of the plumbing out of the way, we can move on to building our authentication functionality. We are going to start off by creating a users module that will contain all of the functionality related to creating and finding users.
Run the following command to create the users module
nest g module Users
By using the g command to generate the module for us, the NestJS CLI will automatically import the module into our root app.module.ts module for us. If you create your modules manually, you will also need to manually add the module to your root module.
We can also use the NestJS CLI to automatically create our users controller for us (which will handle the various routes available on the server) and our users service (which will handle any logic for us).
Run the following commands:
nest g controller users nest g service users
The controller and the service will also automatically be added to the users module since we have used the generate command. We will get to implementing the controller and the service soon, but there are still some additional files we need to create.
Create a file at src/users/dto/create-user.dto.ts and add the following:
export class CreateUserDto { readonly email: string; readonly password: string; }
If you recall, we use DTOs (Data Transfer Objects) to define the structure of the data that we want to be able to POST to our server (from our Ionic application or from whatever frontend we are using). This particular DTO defines the data that we will be sending when we want to create a new user. We also need to define a DTO for when a user wants to log in.
Create a file at src/users/dto/login-user.dto.ts and add the following:
export class LoginUserDto { readonly email: string; readonly password: string; }
These two DTOs are exactly the same, so technically, we don’t really need both in this case. However, it would be common that when creating a user you might also want to accept some additional information (perhaps an age, address, account type, and so on).
Create a file at src/users/user.interface.ts and add the following:
export interface User { email: string }
This file just defines a simple type that we will be able to use with our User objects in the application.
Create a file at src/users/user.schema.ts and add the following:
import * as mongoose from 'mongoose'; import * as bcrypt from 'bcrypt'; export const UserSchema = new mongoose.Schema({ email: { type: String, unique: true, required: true }, password: { type: String, required: true } }); // NOTE: Arrow functions are not used here as we do not want to use lexical scope for 'this' UserSchema.pre('save', function(next){ let user = this; // Make sure not to rehash the password if it is already hashed if(!user.isModified('password')) return next(); // Generate a salt and use it to hash the user's password bcrypt.genSalt(10, (err, salt) => { if(err) return next(err); bcrypt.hash(user.password, salt, (err, hash) => { if(err) return next(err); user.password = hash; next(); }); }); }); UserSchema.methods.checkPassword = function(attempt, callback){ let user = this; bcrypt.compare(attempt, user.password, (err, isMatch) => { if(err) return callback(err); callback(null, isMatch); }); };
Now we are getting into something a bit more substantial, and this is actually a core part of how our authentication system will work. As we have talked about in previous tutorials, a Mongoose schema allows to more easily work with data in our MongoDB database. In this case, we are creating a schema to represent a User.
The interesting part here is the save function we are adding as well as the custom method. With Mongoose, we can specify a function we want to run whenever a document is going to be saved in the database. What will happen here is that when we want to create a new User we will supply the email and the password that the user gives us, but we don’t want to just immediately save that to the database. If we did that, the password would just be stored in plain text - we want to hash the user’s password first.
Hashing is a one-way process (unlike encryption, which can be reversed) that we use to avoid storing a user’s password in plain-text in a database. Instead of checking a user’s password against the value stored in the database directly, we check the hashed version of the password they supply against the hashed version stored in the database. This way, we (as the database owner or anyone with access) won’t be able to actually see what the user’s password is, and perhaps more importantly if the database were compromised by an attacker they would not be able to retrieve the user’s passwords either. You should never store a user’s password without hashing it.
What happens with our save function above is that rather than saving the original value, it will use bcrypt to convert the password field into a hashed version of the value supplied. Before doing that, we use isModified to check if the password value is being changed (if we were just updating some other information on the user, we don’t want to re-hash the password field otherwise they would no longer be able to log in).
We define the checkPassword method on the User schema so that we have an easy way to compare a password from a login attempt to the hashed value that is stored. Before we can continue, we will need to set up our new User schema in our users module (which will allow us to use it in our users service).
Modify src/users/users.module.ts to reflect the following:
import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { PassportModule } from '@nestjs/passport'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { UserSchema } from './user.schema'; @Module({ imports: [ MongooseModule.forFeature([{name: 'User', schema: UserSchema}]), PassportModule.register({ defaultStrategy: 'jwt', session: false }) ], exports: [UsersService], controllers: [UsersController], providers: [UsersService] }) export class UsersModule {}
The important part here is the addition of forFeature which will make our User schema available to use throughout this module. We have also added JWT as the default strategy for the PassportModule here - this is something we are going to get into more in the Auth module, but we will need to add this PassportModule import into any module that contains routes we want to protect with our JWT authorisation. Now we can move on to implementing our service.
Modify src/users/user.service.ts to reflect the following:
import { Model } from 'mongoose'; import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { User } from './user.interface'; import { CreateUserDto } from './dto/create-user.dto'; @Injectable() export class UsersService { constructor(@InjectModel('User') private userModel: Model<User>) {} async create(createUserDto: CreateUserDto) { let createdUser = new this.userModel(createUserDto); return await createdUser.save(); } async findOneByEmail(email): Model<User> { return await this.userModel.findOne({email: email}); } }
As you can see in the constructor we are injecting our User model, and we will be able to use all of the methods that it makes available. We create two functions in this service. The create function will accept the data for creating a new user, and it will use that data to create a new user in MongoDB. The findOneByEmail function will allow us to find a MongoDB record that matches the supplied email address (which will be useful when we are attempting authentication).
Finally, we just need to implement the controller.
Modify src/users/users.controller.ts to reflect the following:
import { Controller, Get, Post, Body } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private usersService: UsersService) { } @Post() async create(@Body() createUserDto: CreateUserDto) { return await this.usersService.create(createUserDto); } }
For now, we just need a single route. This will allow us to make a POST request to /users containing the data required to create a new user. Later, we are going to add an additional “protected” route here that will only be able to be accessed by authorised users.
4. Create the Auth Module
Now we can move on to the Auth module which is going to handle authenticating users, creating JWTs, and checking the validity of JWTs when accessing a protected route. As I mentioned before, we are going to use Passport to create a JWT “strategy” which basically means the code/logic we want to run when attempting to authorise a user for a particular route.
First, we will need to create the module itself:
nest g module Auth
We will also create a controller and service for this module as well:
nest g controller auth nest g service auth
As we had to do in the Users module, we will also need to create some additional files for our Auth module. We will start by creating an interface for our JWTs.
Create a file at src/auth/interfaces/jwt-payload.interface.ts and add the following:
export interface JwtPayload { email: string; }
This is the type for the “payload” of our JWT (i.e. the data contained within the JWT). We are just using an email which we can then use to identify the user by matching it against an email for a user stored in our MongoDB database. You could add other information to your JWT as well, but remember, do not store sensitive information like passwords in a JWT.
Modify src/auth/auth.service.ts to reflect the following:
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { LoginUserDto } from '../users/dto/login-user.dto'; import { UsersService } from '../users/users.service'; import { JwtPayload } from './interfaces/jwt-payload.interface'; @Injectable() export class AuthService { constructor(private usersService: UsersService, private jwtService: JwtService){ } async validateUserByPassword(loginAttempt: LoginUserDto) { // This will be used for the initial login let userToAttempt = await this.usersService.findOneByEmail(loginAttempt.email); return new Promise((resolve) => { // Check the supplied password against the hash stored for this email address userToAttempt.checkPassword(loginAttempt.password, (err, isMatch) => { if(err) throw new UnauthorizedException(); if(isMatch){ // If there is a successful match, generate a JWT for the user resolve(this.createJwtPayload(userToAttempt)); } else { throw new UnauthorizedException(); } }); }); } async validateUserByJwt(payload: JwtPayload) { // This will be used when the user has already logged in and has a JWT let user = await this.usersService.findOneByEmail(payload.email); if(user){ return this.createJwtPayload(user); } else { throw new UnauthorizedException(); } } createJwtPayload(user){ let data: JwtPayload = { email: user.email }; let jwt = this.jwtService.sign(data); return { expiresIn: 3600, token: jwt } } }
Our auth service provides three different methods. The first two handle the two different authentication processes available. We have a validateUserByPassword method that will be used when the user is initially logging in with their email and password. This method will find the user in the MongoDB database that matches the supplied email and then it will invoke the custom checkPassword method we added to the User schema. If the hash of the supplied password matches the hash stored in the database for that user, the authentication will succeed. In that case, the method will return a JWT by calling the createJwtPayload method.
The createJwtPayload method will add the user’s email address to the payload, and then it will sign the JWT using the sign method of the JwtService that was injected into the constructor. This is what ensures that the JWT cannot be tampered with as it is signed by a secret key known only to the server (which we will need to set up in a moment).
The validateUserByJwt method will be used when a user has already logged in and has been given a JWT. This will simply check that the email contained in the JWT represents a real user, and if it does it will return a new JWT (and the success of this method will be used to determine whether or not a user can access a particular route).
Modify src/auth/auth.controller.ts to reflect the following:
import { Controller, Post, Body } from '@nestjs/common'; import { AuthService } from './auth.service'; import { LoginUserDto } from '../users/dto/login-user.dto' @Controller('auth') export class AuthController { constructor(private authService: AuthService) { } @Post() async login(@Body() loginUserDto: LoginUserDto){ return await this.authService.validateUserByPassword(loginUserDto); } }
Our controller is quite simple, we just have a single POST route set up which will allow our frontend application to POST a users email and password to the /auth endpoint. This will then invoke the validateUserByPassword method, and if it is successful it will return the JWT for the user.
Create a file at src/auth/strategies/jwt.strategy.ts and add the following:
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { AuthService } from '../auth.service'; import { PassportStrategy } from '@nestjs/passport'; import { JwtPayload } from '../interfaces/jwt-payload.interface'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService){ super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: 'thisismykickasssecretthatiwilltotallychangelater' }); } async validate(payload: JwtPayload){ const user = await this.authService.validateUserByJwt(payload); if(!user){ throw new UnauthorizedException(); } return user; } }
This is the “strategy” that we will use to authorise a user when they are attempting to access a protected route - this is where the Passport package comes in. In the constructor, we supply passport with the settings we want to use. We will be extracting the JWT from the Bearer header that we will send along with any requests to the server, and the secret key that we will use to sign the JWTs is thisismykickasssecretthatiwilltotallychangelater. You need to change this to a secret value that isn’t known to anybody else - if somebody knows the secret key you are using to sign your JWTs then they can easily create their own JWTs containing whatever information they like, and your server will see those JWTs as valid.
IMPORTANT: Don’t forget to change the secretOrKey value.
The validate method handles checking that the JWT supplied is valid by invoking the validateUserByJwt method that we created earlier.
Modify src/auth/auth.module.ts to reflect the following:
import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { JwtStrategy } from './strategies/jwt.strategy'; import { UsersModule } from '../users/users.module'; import { PassportModule } from '@nestjs/passport'; @Module({ imports: [ PassportModule.register({ defaultStrategy: 'jwt', session: false }), JwtModule.register({ secretOrPrivateKey: 'thisismykickasssecretthatiwilltotallychangelater', signOptions: { expiresIn: 3600 } }), UsersModule ], controllers: [AuthController], providers: [AuthService, JwtStrategy] }) export class AuthModule {}
Now we just need to make a few changes to our Auth module. Once again, we set up the PassportModule with the default strategy that will be used as we need to import this into any module where we want to add protected routes. We set up the NestJS JwtModule with the values we want to use with our JWT (make sure to use the same secret key value as before, but make sure it is different to the one I am using).
5. Create a Restricted Route
With everything above in place, we now have an API that supports:
Creating new users
Authenticating users with an email and password
Supplying users with a JWT
Authenticating users with a JWT
Restricting particular routes by enforcing that users require a valid JWT in order to access it
Although this is in place, we don’t actually have any protected routes. Let’s create a test route in our users controller.
Modify src/users/users.controller.ts to reflect the following:
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UsersService } from './users.service'; import { AuthGuard } from '@nestjs/passport'; @Controller('users') export class UsersController { constructor(private usersService: UsersService) { } @Post() async create(@Body() createUserDto: CreateUserDto) { return await this.usersService.create(createUserDto); } // This route will require successfully passing our default auth strategy (JWT) in order // to access the route @Get('test') @UseGuards(AuthGuard()) testAuthRoute(){ return { message: 'You did it!' } } }
To protect a particular route, all we need to do is add @UseGuards(@AuthGuard()) to it and it will use our default JWT strategy to protect that route. With this in place, if we were to make a GET request to /users/test it would only work if we sent the JWT along with the request in the headers.
6. Test in an Ionic Application
With everything in place, let’s test it!
Everything we have done above really doesn’t have much to do with Ionic at all, you could use this backend with many types of frontends. In the end, all we will be doing is making GET and POST HTTP requests to the NestJS backend. Although you do not have to use Ionic, I am going to show you some rough code that you can use to test the functionality.
NOTE: The following code is purely for testing the API and is in no way designed well. Your Ionic application should not look like this. Keep in mind that you will need to have the HttpClientModule set up in order for the following code to work.
Modify src/home/home.page.ts to reflect the following:
import { Component } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'], }) export class HomePage { public createEmail: string; public createPassword: string; public signInEmail: string; public signInPassword: string; public jwt: string; constructor(private http: HttpClient){ } createAccount(){ let credentials = { email: this.createEmail, password: this.createPassword } this.http.post('http://localhost:3000/users', credentials).subscribe((res) => { console.log(res); }); } signIn(){ let credentials = { email: this.signInEmail, password: this.signInPassword } this.http.post('http://localhost:3000/auth', credentials).subscribe((res: any) => { console.log(res); // NOTE: This is just for testing, typically you would store the JWT in local storage and retrieve from there this.jwt = res.token; }); } testRoute(){ let headers = new HttpHeaders().set('Authorization', 'Bearer ' + this.jwt) this.http.get('http://localhost:3000/users/test', {headers: headers}).subscribe((res) => { console.log(res); }); } logout(){ this.jwt = null; } }
Modify src/home/home.page.html to reflect the following:
<ion-header> <ion-toolbar> <ion-title> Ionic Blank </ion-title> </ion-toolbar> </ion-header> <ion-content padding> <h2>Create Account</h2> <ion-input [(ngModel)]="createEmail" type="text" placeholder="email"></ion-input> <ion-input [(ngModel)]="createPassword" type="password" placeholder="password"></ion-input> <ion-button (click)="createAccount()" color="primary">Test Create Account</ion-button> <h2>Sign In</h2> <ion-input [(ngModel)]="signInEmail" type="text" placeholder="email"></ion-input> <ion-input [(ngModel)]="signInPassword" type="password" placeholder="password"></ion-input> <ion-button (click)="signIn()" color="primary">Test Sign In</ion-button> <ion-button (click)="testRoute()" color="light">Test Protected Route</ion-button> <ion-button (click)="logout()" color="light">Test Logout</ion-button> </ion-content>
The important part in the code above is that we are setting the Authorization header and sending our JWT as a bearer token. In order to run this example, you will need to make sure that you:
Have the MongoDB daemon running with mongod
Have your NestJS backend being served by running npm run start
Have your Ionic application served with ionic serve
If you are using the code above you should be able to go through the process of:
Creating a user
Signing in with that user
Accessing the protected route
Logging out (and you will no longer be able to access the protected route)
After creating a user or signing in, you should receive a JWT in the server response that will look something like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZ21haWwuY29tIiwiaWF0IjoxNTQ0NTcyNzM3LCJleHAiOjE1NDQ1NzYzMzd9.6p0XH9KkGsde9S38nDOkPudYk02dZK6xxtd3qqWFg3M
If you were to paste this JWT in the debugger at jwt.io you would be able to see that the payload of this JWT is:
{ "email": "[email protected]", "iat": 1544572737, "exp": 1544576337 }
However, you might notice that it says “Invalid Signature”. This is good, we should only get a valid signature when the secret key the JWT was signed with is supplied. If you were to first add the key thisismykickasssecretthatiwilltotallychangelater to the “Verify Signature” section under “your-256-bit-secret”, and then add the JWT to the “Encoded” section you will see that the signature is valid. Since this key is only known to our server, only our server can validate or create JWTs.
In the test code, we are just saving the JWT on this.jwt but you would typically save this in some kind of local storage.
If you wanted to test the security of the application you could even try modifying the values of the JWT manually, or supplying your own manually created JWTs to see if you can fool the authorisation process. After you have created a user, if you were to open a MongoDB shell in your terminal by running mongo you should be able to run the following commands:
use authexample users.find()
To retrieve a list of all of the users you have created. The users will look something like this:
{ "_id" : ObjectId("5c0dc18d349bc9479600c171"), "email" : "[email protected]", "password" : "$2b$10$2WLRRE/IWW.1yXcEyt0sZeFS/257w6SAaigbMNMfcqX1JNZ1KKXGO", "__v" : 0 }
You can see the password hashing in action here. I’ll let you in on a little secret - the test password I used for this was password. But, there is no way you could know that just by looking at the value stored in the database:
$2b$10$2WLRRE/IWW.1yXcEyt0sZeFS/257w6SAaigbMNMfcqX1JNZ1KKXGO
Since this value is hashed (not encrypted) it means that this value should not be able to be reversed. We can’t read what the password is, and neither could an attacker.
Summary
This tutorial wasn’t exactly a simple one but without too much work we now have a fully functional authentication/authorisation system that can remember users after their initial login. I’d like to stress again that this is primarily for educational purposes and has not been seriously tested - please make sure to do your own research and testing if you intend to use any of this code in a production environment.
via joshmorony - Learn Ionic & Build Mobile Apps with Web Tech https://ift.tt/2C7R8CG
0 notes
Text
Getting Started with Nest.js
If you have ever worked on a Node.js application before, either built a REST API or an enterprise application, you must have realised how tedious and daunting it was to maintain, especially whenever the application start to scale. The more you add new features to the application, the larger the codebase.
Creating a proper structure for such application can result into serious headache if not properly managed, especially as a result of application specific configurations. This is why Nest.js was created.
Nest.js was built mainly to eliminate disorganized codebases and give Node.js application a moderate and reasonable structure out of the box. Heavily inspired by Angular, Nest.js was built with TypeScript and uses Express.js under hood. This rightly makes it compatible with the majority of express middleware.
In this post, I will introduce and take you through the process of getting started with Nest.js. You will learn about several ways to install the framework on your machine and why you need to consider using it for your next project. In the process of doing this, you will create a very simple RESTful API that enables users to fetch, create and delete books in a bookstore.
This is a very simple application but yet broad enough to give you comprehensive insight on how to craft an application with Nest.js.
Familiarity with TypeScript and a reasonable knowledge of JavaScript will help you get the best out of this tutorial. Experienced with building applications with Angular will be a plus but not a requirement as the article will give you a proper guide on how to easily begin.
You need to install Node and npm. It is advisable to also install nodemon globally on your machine.
Nest.js is a server-side Node.js framework for building efficient, reliable and scalable applications. Built by Kamil and backed by quite a number of reputable organizations and individuals.
Nest.js was introduced to solve the architectural problem of Node.js by giving backend applications a modular structure for organising code into separate modules.
Fully built with TypeScript, it comes with the benefits of code type checking and dependency injection which helps to facilitate the process of development of applications. If you are conversant with the structure of Angular applications, you are going to feel so comfortable with the key concepts of Nest.js and getting started with it will be quite an easy task. Anyways, this post will provide you with the required details needed to start building applications with Nest.js.
In addition, the following list shows some of the benefits of Nest.js as explained here by Kamil:
it surrounds your route handler body with try..catch blocks
it makes every route handler async
it creates a global express router
Free Node eBook
Build your first Node apps and learn server-side JavaScript.
Thank you!
You have successfully joined the Scotchy super duper web dev awesome mailing list.
it creates a separated router for each controller
it binds error-handling middleware
it binds body-parser middleware (both json and extended urlencoded)
Now that you have been briefed about this awesome framework, let’s take a look at the building blocks of Nest.js.
The following are the building blocks used when building Nest.js applications:
Controllers
Typical to most web frameworks, controllers in Nest.js are responsible for handling any incoming requests and returning responses to the client side of the application. For example, if you make an API call to a particular endpoint, say /home, the controller will receive this request and based on the available resources, it will returned the appropriate response.
Nest.js was structured in a way that the routing mechanism is able to control which controller will be responsible for handling a particular request.
Defining a basic controller in Nest.js is as good as creating a TypeScript file and including a decorator @Controller() just like the code snippet below:
// users.controller.ts import { Controller, Get } from '@nestjs/common'; @Controller('users') export class UsersController { @Get() findAll() { return 'This will return all the users'; } }
The prefix of users within the Controller decorator will prompt the UsersController to handle any /users GET request within an application and return the appropriate response as specified. Other HTTP request handled by the controller includes POST , PUT, DELETE as we will see later in the tutorial.
Once a controller is created, it needs to be added to the module definition before Nest.js can easily recognise it. This could be the root ApplicationModule or any other module created within the application. More about this in the module section of this post.
As mentioned earlier, Nest.js was heavily inspired by Angular and similar to an Angular application, one can easily create a provider and inject it into controllers or other providers too as well. These providers are also called services and based on the philosophy of Nest.js, it was designed to abstract any form of complexity and logic to a class called service.
A service provider in Nest.js is just a normal JavaScript class with a special @Injectable() decorator at the top.
For example, you can simply create a service to fetch users as shown below:
// users.service.ts import { Injectable } from '@nestjs/common'; import { User } from './interfaces/user.interface'; @Injectable() export class UsersService { private readonly users: User[] = []; create(user: User) { this.users.push(user); } findAll(): User[] { return this.users; } }
The provider created above is a class with two methods create() and findAll(), which can be used to create and return all users respectively. And to easily help with type checking an interface was used to specify the type of elements that should be received by the methods.
Modules are more like the most important basic building block in Nest.js. They are TypeScript files decorated with @Module decorator. This attached decorator provides metadata that Nest makes use of to organize the application structure. With modules you can easily group related files into one.
Each Nest.js application must have at least one module, usually referred to as the root module. This root module is the top-level module and usually enough for a small application but it is advisable to break a large application into multiple modules as it helps to maintain the structure of the application.
If you have an application that manages a lot of data or functionality about users , we can group both the controller, services and other related files into a single module, say UsersModule for example:
import { Module } from '@nestjs/common'; import { UsersController } from './users.controller.ts'; import { UsersService } from './users.service.ts'; @Module({ controllers: [UsersController], providers: [UsersService] }) export class UsersModule {}
From the preceding file, we are exported a UsersModule that contains both the UsersController and UsersService. With this in place, we can then proceed to import and use the UsersModule within the root module of the application as shown in the following code snippet:
... import { UsersModule } from './users/users.module'; @Module({ ... }) export class AppModule { }
DTO
Data transfer object is an object that defines how data will be sent over the network.
Interfaces
TypeScript interfaces are used for type-checking and defining the types of data that can be passed to a controller or a Nest service.
Dependency injection
Dependency injection is a design pattern used to increase efficiency and modularity of applications. It is often used by the biggest frameworks to keep code clean and easier to use. Nest.js also makes use of it to basically create coupled components.
With this pattern, it is very easy to manage dependencies between building blocks like controllers, providers and modules. The only thing required is to define the dependency for example a UsersService() in the constructor of a controller as shown here:
... @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService){} ... }
With some of these concepts briefly covered, you can now proceed to the next section, where you will put all the knowledge gained so far in this post into use as you will learn how to seamlessly build a RESTful API using Nest.js.
As stated earlier in this post, you will create a sample application that will help you get a good grasp on some of the core concepts of Nest.js.
This application will be specifically for a bookstore. At the end of the post you would have created a micro-service that will enable users to create and add a new book with few descriptions to an existing list of books. This could be from a database, but to ensure simplicity in this post, we won’t really be connecting our application to a database yet. But instead, we will make use of a mock data of books and once a new book is created, we will push and add it to the list.
In order to easily scaffold a new Nest.js application, you will need to globally installed Nest CLI. It is a command line interface tool specifically created to amongst other things, help to craft a new Nest.js app in no time and provide access to ( built in generators ) several commands to generate different files and produce a well-structured application.
Apart from using the CLI tool, you can also install a new Nest.js application by cloning the starter project from GitHub using Git, but for the purpose of this tutorial run the following command to install the Nest CLI:
npm i -g @nestjs/cli
This will give you access to the nest command for project installation and other project specific commands.
Next, run the command below to install a new project named bookstore-nest within your development folder:
nest new bookstore-nest
You will be asked few questions during the installation, just follow the prompt and respond accordingly. Next, once the installation is complete, change directory into the newly created project and start the application with:
// change directory cd bookstore-nest // start the application npm run start
or better still, run the command below in order to use Nodemon for the project:
// start the application using nodemon npm run start:dev
Navigate to http://localhost:3000 from your favorite browser, you will see the Hello World! message as shown here:
First you will start by generating a module for the bookstore. To do this, you will leverage the inbuilt file generator using Nest CLI. Run the following command to scaffold a new module for the application:
nest generate module books
The command above will create a new folder named books within the src folder. Also within the books folder you will find a books.module.ts file.
// ./src/books/books/module.ts import { Module } from '@nestjs/common'; @Module({}) export class BooksModule {}
This was generated by the command and the module has also been added to the app.module.ts which happens to be the root module of the application.
Next, you will create routes for the endpoints. As mentioned earlier, routes are in controllers, so you need to create controllers that will handle individual endpoints. Again, use Nest CLI to generate your controllers, run the following command:
nest generate controller books
This will create a controller inside the books folder. Since we won’t really be connecting to the database for now, create a sample mock data for the bookstore. Under the src folder, create a subfolder named mocks and within the newly created folder, create a new TypeScript file named books.mock.ts and paste the following code in it:
// ./src/mocks/books.mock.ts export const BOOKS = [ { id: 1, title: 'First book', description: "This is the description for the first book", author: 'Olususi Oluyemi' }, { id: 2, title: 'Second book', description: "This is the description for the second book", author: 'John Barry' }, { id: 3, title: 'Third book', description: "This is the description for the third book", author: 'Clement Wilfred' }, { id: 4, title: 'Fourth book', description: "This is the description for the fourth book", author: 'Christian nwamba' }, { id: 5, title: 'Fifth book', description: "This is the description for the fifth book", author: 'Chris anderson' }, { id: 6, title: 'Sixth book', description: "This is the description for the sixth book", author: 'Olususi Oluyemi' }, ];
Next, you will create a service to hold all the logic for the bookstore. Run the following command to generate a service:
nest generate service books
This command will create a new file named books.service.ts within ./src/books folder.
Next, open the newly created file and paste the following:
// ./src/books/books.service.ts import { Injectable, HttpException } from '@nestjs/common'; import { BOOKS } from '../mocks/books.mock'; @Injectable() export class BooksService { books = BOOKS; getBooks(): Promise<any> { return new Promise(resolve => { resolve(this.books); }); } getBook(bookID): Promise<any> { let id = Number(bookID); return new Promise(resolve => { const book = this.books.find(book => book.id === id); if (!book) { throw new HttpException('Book does not exist!', 404); } resolve(book); }); } }
First, you imported the requires modules from Nest.js and also BOOKS from the mock data you created earlier.
Next, you created two different methods named getBooks() and getBook() to retrieve the list of books from the mock data and to fetch just one book using the bookID as a parameter.
Next, add the method below to the /src/books/books.service.ts immediately after the getBook() method:
// ./src/books/books.service.ts import { Injectable, HttpException } from '@nestjs/common'; import { BOOKS } from '../mocks/books.mock'; @Injectable() export class BooksService { books = BOOKS; ... addBook(book): Promise<any> { return new Promise(resolve => { this.books.push(book); resolve(this.books); }); } }
The method above will be used to push a new book to the existing list
Finally, add the last method to delete a particular book using the bookID as a parameter:
// ./src/books/books.service.ts import { Injectable, HttpException } from '@nestjs/common'; import { BOOKS } from '../mocks/books.mock'; @Injectable() export class BooksService { books = BOOKS; ... deleteBook(bookID): Promise<any> { let id = Number(bookID); return new Promise(resolve => { let index = this.books.findIndex(book => book.id === id); if (index === -1) { throw new HttpException('Book does not exist!', 404); } this.books.splice(1, index); resolve(this.books); }); } }
Here, you will use dependency injection design pattern to pass the BooksService into the BooksController through a constructor. Open the BooksController created earlier and paste the following code in it:
// ./src/books/books.controller.ts import { Controller, Get, Param, Post, Body, Query, Delete } from '@nestjs/common'; import { BooksService } from './books.service'; import { CreateBookDTO } from './dto/create-book.dto'; @Controller('books') export class BooksController { constructor(private booksService: BooksService) { } @Get() async getBooks() { const books = await this.booksService.getBooks(); return books; } @Get(':bookID') async getBook(@Param('bookID') bookID) { const book = await this.booksService.getBook(bookID); return book; } @Post() async addBook(@Body() createBookDTO: CreateBookDTO) { const book = await this.booksService.addBook(createBookDTO); return book; } @Delete() async deleteBook(@Query() query) { const books = await this.booksService.deleteBook(query.bookID); return books; } }
Here in this controller, first, the important modules were imported from @nestjs/common and you also import both the BooksService and CreateBookDTO respectively. CreateBookDTO is a data transfer object, a TypeScript class created for type-checking and to define the structures of what an object looks like when creating a new book. We will create this DTO in a bit.
Next, you used constructor to inject the BooksService into the controller and created four different methods which are:
getBooks(): Used to fetch the list of all books. It has @Get() decorator attached to it. This helps to map any GET request sent to /books to this controller.
getBook(): Used to retrieve the details of a particular book by passing the bookID as a parameter.
addBook(): Used to create and post a new book to the existing book list. And because we are not persisting into the database, the newly added book will only be held in memory.
deleteBook(): Used to delete a book by passing the bookID as a query parameter.
Each of the methods has a special decorator attached to it, which makes it very easy to route each HTTP request to a specific method within the controller.
In the previous section, you made use of a data transfer object called CreateBookDTO. To set it up, navigate to the ./src/books folder and create a new subfolder name dto. Next, within the newly created folder, create another file and call it create-book.dto.ts and paste the following in it:
// ./src/books/dto/create-book.dto.ts export class CreateBookDTO { readonly id: number; readonly title: string; readonly description: string; readonly author: string; }
You are almost done with the application, the next line of action is to take a look at the BooksModule and update it accordingly. You will do that in the next section.
Navigate back to the BooksModule created earlier and update it with the code below:
// ./src/books/books.module.ts import { Module } from '@nestjs/common'; import { BooksController } from './books.controller'; import { BooksService } from './books.service'; @Module({ controllers: [BooksController], providers: [BooksService] }) export class BooksModule {}
Start the application again if it is not running at the moment with:
npm run start
and use postman to test the API
We have barely scratched the surface on what Nest.js has to offer the Node.js world in this post. To get more conversant with this awesome framework, first, we took a quick look at the fundamentals and basic building blocks of Nest.js and then proceeded to build a RESTful API where you also learnt about dependency injection amongst other things.
I hope this tutorial as given you enough information to try out Nest.js for your next application. Feel free to drop your thoughts in the comment section below and find the complete source code of this tutorial here on GitHub.
via Scotch.io http://bit.ly/2R5NVbh
0 notes
Text
Adding JWT Authentication to an Ionic Application with MongoDB and NestJS
Many Ionic applications will require the concept of an authenticated user who is authorised to perform certain actions – perhaps only authenticated users may access your application at all, or perhaps you want to restrict certain types of functionality in your application to certain types of users.
There are many different options that can be used to add this functionality to your application, including services like Firebase and Auth0 which do most of the heavy lifting for you and provide a simple API for authenticating users. However, you can also create your own authentication/authorisation functionality, which may be a more attractive option if you are taking a self-hosted approach to your backend.
In this tutorial, we are going to cover how to create our own authentication system for an Ionic application with MongoDB (to store users other data) and NestJS (to handle HTTP requests to the backend). The basic flow will work like this:
A user will create an account (if they do not have one already)
The account will be stored in the MongoDB database (if it has not been already)
The user will supply their email and password to sign in to the application
If the sign in is successful, the user will be supplied with a JWT that identifies them
Requests to any restricted routes will require that the JWT is sent along with the request in order to access it
I will not be covering what JSON Web Tokens (JWT) are in this tutorial. If you are not already familiar with the concept, I would recommend reading this article. In short, a JWT supplied to a user cannot be modified by the user (without breaking the signature), and so if we supply a JWT to the user that says they are Natalie, we can trust that is true since we know the user couldn’t have just edited it themselves on the client-side. If the someone were to try to modify a JWT (e.g. changing Natalie to Josh) then when the JWT is checked on the server we would be able to tell that it was tampered with (and thus reject the request). This concept means we can check a users authorisation without needing their password after they have signed in initially.
A critical point about a JWT is that they cannot be modified not that they can’t be read. A JWT is encoded and that may give the illusion that you could store sensitive data in the JWT, but you should definitely never do this as a JWT can be easily decoded by anybody. A JWT is good for storing information like a user_id, an email, or a username, but never something sensitive like a password. Another important thing to keep in mind about a JWT is that anybody who has it has the ability to be authorised as the “true” owner of the JWT (e.g. if someone managed to steal a JWT from someone, they may be able to perform actions that they should not be authorised to do). We will be taking a very simple approach in this tutorial, but keep in mind that other security measures are often used in conjunction with a JWT.
IMPORTANT: This tutorial is for learning purposes only. It has not been rigorously tested and it is not a plug-and-play solution. Authentication/authorisation and security in general, are important and complex topics. If you are creating anything where security is important, you should always engage the help of an expert with relevant knowledge.
Before We Get Started
Although this tutorial is completely standalone, it continues on from the concepts that we have covered in the previous NestJS tutorials:
An Introduction to NestJS for Ionic Developers
Using Providers and HTTP Requests in a NestJS Backend
Sending Data with POST Requests to a NestJS Backend
Using MongoDB with Ionic and NestJS
If you have not already read these, or you are not already familiar with the basic NestJS concepts, I would recommend that you read those first as I won’t be explaining those concepts in this tutorial.
In order to work with MongoDB on your machine, you will need to have it installed. If you do not already have MongoDB installed on your machine, you can find information on how to do that here. I also released a video recently that covers installing MongoDB on macOS: Installing MongoDB with Homebrew on macOS.
Once you have installed MongoDB, you will need to make sure to open a separate terminal window and run the following command:
mongod
This will start the MongoDB daemon, meaning that the database will be running in the background on your computer and you will be able to interact with it.
1. Create a New NestJS Application
We will start by creating a fresh new NestJS application, which we can do with the following command:
nest new nest-jwt-auth
We are also going to take care of installing all of the dependencies we require as well.
npm install --save @nestjs/mongoose mongoose
This will install mongoose and the associated NestJS package that we will use to interact with our MongoDB database.
npm install --save bcrypt
We will be using bcrypt to hash the user passwords that we will be storing in the database.
npm install --save passport npm install --save passport-jwt npm install --save @nestjs/jwt npm install --save @nestjs/passport
We will be using the Passport library to implement authentication “strategies” – this helps us define the process that will be used to determine whether a user is authorised to access certain routes or not. We will be implementing a JWT strategy, so we also require the JWT packages.
Before we move on, we are also going to enable CORS which will allow us to make cross-domain requests to our NestJS server.
Modify src/main.ts to reflect the following:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); await app.listen(3000); } bootstrap();
2. Set up the MongoDB Connection
In order to interact with our MongoDB database, we will need to set up the connection in the root module of our NestJS application.
Modify src/app.module.ts to reflect the following:
import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [ MongooseModule.forRoot('mongodb://localhost/authexample') ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
In this particular example, we will be using a database called authexample but you can name this whatever you like (there is no need to “create” the database beforehand).
3. Create the Users Module
Now that we have all of the plumbing out of the way, we can move on to building our authentication functionality. We are going to start off by creating a users module that will contain all of the functionality related to creating and finding users.
Run the following command to create the users module
nest g module Users
By using the g command to generate the module for us, the NestJS CLI will automatically import the module into our root app.module.ts module for us. If you create your modules manually, you will also need to manually add the module to your root module.
We can also use the NestJS CLI to automatically create our users controller for us (which will handle the various routes available on the server) and our users service (which will handle any logic for us).
Run the following commands:
nest g controller users nest g service users
The controller and the service will also automatically be added to the users module since we have used the generate command. We will get to implementing the controller and the service soon, but there are still some additional files we need to create.
Create a file at src/users/dto/create-user.dto.ts and add the following:
export class CreateUserDto { readonly email: string; readonly password: string; }
If you recall, we use DTOs (Data Transfer Objects) to define the structure of the data that we want to be able to POST to our server (from our Ionic application or from whatever frontend we are using). This particular DTO defines the data that we will be sending when we want to create a new user. We also need to define a DTO for when a user wants to log in.
Create a file at src/users/dto/login-user.dto.ts and add the following:
export class LoginUserDto { readonly email: string; readonly password: string; }
These two DTOs are exactly the same, so technically, we don’t really need both in this case. However, it would be common that when creating a user you might also want to accept some additional information (perhaps an age, address, account type, and so on).
Create a file at src/users/user.interface.ts and add the following:
export interface User { email: string }
This file just defines a simple type that we will be able to use with our User objects in the application.
Create a file at src/users/user.schema.ts and add the following:
import * as mongoose from 'mongoose'; import * as bcrypt from 'bcrypt'; export const UserSchema = new mongoose.Schema({ email: { type: String, unique: true, required: true }, password: { type: String, required: true } }); // NOTE: Arrow functions are not used here as we do not want to use lexical scope for 'this' UserSchema.pre('save', function(next){ let user = this; // Make sure not to rehash the password if it is already hashed if(!user.isModified('password')) return next(); // Generate a salt and use it to hash the user's password bcrypt.genSalt(10, (err, salt) => { if(err) return next(err); bcrypt.hash(user.password, salt, (err, hash) => { if(err) return next(err); user.password = hash; next(); }); }); }); UserSchema.methods.checkPassword = function(attempt, callback){ let user = this; bcrypt.compare(attempt, user.password, (err, isMatch) => { if(err) return callback(err); callback(null, isMatch); }); };
Now we are getting into something a bit more substantial, and this is actually a core part of how our authentication system will work. As we have talked about in previous tutorials, a Mongoose schema allows to more easily work with data in our MongoDB database. In this case, we are creating a schema to represent a User.
The interesting part here is the save function we are adding as well as the custom method. With Mongoose, we can specify a function we want to run whenever a document is going to be saved in the database. What will happen here is that when we want to create a new User we will supply the email and the password that the user gives us, but we don’t want to just immediately save that to the database. If we did that, the password would just be stored in plain text – we want to hash the user’s password first.
Hashing is a one-way process (unlike encryption, which can be reversed) that we use to avoid storing a user’s password in plain-text in a database. Instead of checking a user’s password against the value stored in the database directly, we check the hashed version of the password they supply against the hashed version stored in the database. This way, we (as the database owner or anyone with access) won’t be able to actually see what the user’s password is, and perhaps more importantly if the database were compromised by an attacker they would not be able to retrieve the user’s passwords either. You should never store a user’s password without hashing it.
What happens with our save function above is that rather than saving the original value, it will use bcrypt to convert the password field into a hashed version of the value supplied. Before doing that, we use isModified to check if the password value is being changed (if we were just updating some other information on the user, we don’t want to re-hash the password field otherwise they would no longer be able to log in).
We define the checkPassword method on the User schema so that we have an easy way to compare a password from a login attempt to the hashed value that is stored. Before we can continue, we will need to set up our new User schema in our users module (which will allow us to use it in our users service).
Modify src/users/users.module.ts to reflect the following:
import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { PassportModule } from '@nestjs/passport'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { UserSchema } from './user.schema'; @Module({ imports: [ MongooseModule.forFeature([{name: 'User', schema: UserSchema}]), PassportModule.register({ defaultStrategy: 'jwt', session: false }) ], exports: [UsersService], controllers: [UsersController], providers: [UsersService] }) export class UsersModule {}
The important part here is the addition of forFeature which will make our User schema available to use throughout this module. We have also added JWT as the default strategy for the PassportModule here – this is something we are going to get into more in the Auth module, but we will need to add this PassportModule import into any module that contains routes we want to protect with our JWT authorisation. Now we can move on to implementing our service.
Modify src/users/user.service.ts to reflect the following:
import { Model } from 'mongoose'; import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { User } from './user.interface'; import { CreateUserDto } from './dto/create-user.dto'; @Injectable() export class UsersService { constructor(@InjectModel('User') private userModel: Model<User>) {} async create(createUserDto: CreateUserDto) { let createdUser = new this.userModel(createUserDto); return await createdUser.save(); } async findOneByEmail(email): Model<User> { return await this.userModel.findOne({email: email}); } }
As you can see in the constructor we are injecting our User model, and we will be able to use all of the methods that it makes available. We create two functions in this service. The create function will accept the data for creating a new user, and it will use that data to create a new user in MongoDB. The findOneByEmail function will allow us to find a MongoDB record that matches the supplied email address (which will be useful when we are attempting authentication).
Finally, we just need to implement the controller.
Modify src/users/users.controller.ts to reflect the following:
import { Controller, Get, Post, Body } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private usersService: UsersService) { } @Post() async create(@Body() createUserDto: CreateUserDto) { return await this.usersService.create(createUserDto); } }
For now, we just need a single route. This will allow us to make a POST request to /users containing the data required to create a new user. Later, we are going to add an additional “protected” route here that will only be able to be accessed by authorised users.
4. Create the Auth Module
Now we can move on to the Auth module which is going to handle authenticating users, creating JWTs, and checking the validity of JWTs when accessing a protected route. As I mentioned before, we are going to use Passport to create a JWT “strategy” which basically means the code/logic we want to run when attempting to authorise a user for a particular route.
First, we will need to create the module itself:
nest g module Auth
We will also create a controller and service for this module as well:
nest g controller auth nest g service auth
As we had to do in the Users module, we will also need to create some additional files for our Auth module. We will start by creating an interface for our JWTs.
Create a file at src/auth/interfaces/jwt-payload.interface.ts and add the following:
export interface JwtPayload { email: string; }
This is the type for the “payload” of our JWT (i.e. the data contained within the JWT). We are just using an email which we can then use to identify the user by matching it against an email for a user stored in our MongoDB database. You could add other information to your JWT as well, but remember, do not store sensitive information like passwords in a JWT.
Modify src/auth/auth.service.ts to reflect the following:
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { LoginUserDto } from '../users/dto/login-user.dto'; import { UsersService } from '../users/users.service'; import { JwtPayload } from './interfaces/jwt-payload.interface'; @Injectable() export class AuthService { constructor(private usersService: UsersService, private jwtService: JwtService){ } async validateUserByPassword(loginAttempt: LoginUserDto) { // This will be used for the initial login let userToAttempt = await this.usersService.findOneByEmail(loginAttempt.email); return new Promise((resolve) => { // Check the supplied password against the hash stored for this email address userToAttempt.checkPassword(loginAttempt.password, (err, isMatch) => { if(err) throw new UnauthorizedException(); if(isMatch){ // If there is a successful match, generate a JWT for the user resolve(this.createJwtPayload(userToAttempt)); } else { throw new UnauthorizedException(); } }); }); } async validateUserByJwt(payload: JwtPayload) { // This will be used when the user has already logged in and has a JWT let user = await this.usersService.findOneByEmail(payload.email); if(user){ return this.createJwtPayload(user); } else { throw new UnauthorizedException(); } } createJwtPayload(user){ let data: JwtPayload = { email: user.email }; let jwt = this.jwtService.sign(data); return { expiresIn: 3600, token: jwt } } }
Our auth service provides three different methods. The first two handle the two different authentication processes available. We have a validateUserByPassword method that will be used when the user is initially logging in with their email and password. This method will find the user in the MongoDB database that matches the supplied email and then it will invoke the custom checkPassword method we added to the User schema. If the hash of the supplied password matches the hash stored in the database for that user, the authentication will succeed. In that case, the method will return a JWT by calling the createJwtPayload method.
The createJwtPayload method will add the user’s email address to the payload, and then it will sign the JWT using the sign method of the JwtService that was injected into the constructor. This is what ensures that the JWT cannot be tampered with as it is signed by a secret key known only to the server (which we will need to set up in a moment).
The validateUserByJwt method will be used when a user has already logged in and has been given a JWT. This will simply check that the email contained in the JWT represents a real user, and if it does it will return a new JWT (and the success of this method will be used to determine whether or not a user can access a particular route).
Modify src/auth/auth.controller.ts to reflect the following:
import { Controller, Post, Body } from '@nestjs/common'; import { AuthService } from './auth.service'; import { LoginUserDto } from '../users/dto/login-user.dto' @Controller('auth') export class AuthController { constructor(private authService: AuthService) { } @Post() async login(@Body() loginUserDto: LoginUserDto){ return await this.authService.validateUserByPassword(loginUserDto); } }
Our controller is quite simple, we just have a single POST route set up which will allow our frontend application to POST a users email and password to the /auth endpoint. This will then invoke the validateUserByPassword method, and if it is successful it will return the JWT for the user.
Create a file at src/auth/strategies/jwt.strategy.ts and add the following:
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { AuthService } from '../auth.service'; import { PassportStrategy } from '@nestjs/passport'; import { JwtPayload } from '../interfaces/jwt-payload.interface'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService){ super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: 'thisismykickasssecretthatiwilltotallychangelater' }); } async validate(payload: JwtPayload){ const user = await this.authService.validateUserByJwt(payload); if(!user){ throw new UnauthorizedException(); } return user; } }
This is the “strategy” that we will use to authorise a user when they are attempting to access a protected route – this is where the Passport package comes in. In the constructor, we supply passport with the settings we want to use. We will be extracting the JWT from the Bearer header that we will send along with any requests to the server, and the secret key that we will use to sign the JWTs is thisismykickasssecretthatiwilltotallychangelater. You need to change this to a secret value that isn’t known to anybody else – if somebody knows the secret key you are using to sign your JWTs then they can easily create their own JWTs containing whatever information they like, and your server will see those JWTs as valid.
IMPORTANT: Don’t forget to change the secretOrKey value.
The validate method handles checking that the JWT supplied is valid by invoking the validateUserByJwt method that we created earlier.
Modify src/auth/auth.module.ts to reflect the following:
import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { JwtStrategy } from './strategies/jwt.strategy'; import { UsersModule } from '../users/users.module'; import { PassportModule } from '@nestjs/passport'; @Module({ imports: [ PassportModule.register({ defaultStrategy: 'jwt', session: false }), JwtModule.register({ secretOrPrivateKey: 'thisismykickasssecretthatiwilltotallychangelater', signOptions: { expiresIn: 3600 } }), UsersModule ], controllers: [AuthController], providers: [AuthService, JwtStrategy] }) export class AuthModule {}
Now we just need to make a few changes to our Auth module. Once again, we set up the PassportModule with the default strategy that will be used as we need to import this into any module where we want to add protected routes. We set up the NestJS JwtModule with the values we want to use with our JWT (make sure to use the same secret key value as before, but make sure it is different to the one I am using).
5. Create a Restricted Route
With everything above in place, we now have an API that supports:
Creating new users
Authenticating users with an email and password
Supplying users with a JWT
Authenticating users with a JWT
Restricting particular routes by enforcing that users require a valid JWT in order to access it
Although this is in place, we don’t actually have any protected routes. Let’s create a test route in our users controller.
Modify src/users/users.controller.ts to reflect the following:
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UsersService } from './users.service'; import { AuthGuard } from '@nestjs/passport'; @Controller('users') export class UsersController { constructor(private usersService: UsersService) { } @Post() async create(@Body() createUserDto: CreateUserDto) { return await this.usersService.create(createUserDto); } // This route will require successfully passing our default auth strategy (JWT) in order // to access the route @Get('test') @UseGuards(AuthGuard()) testAuthRoute(){ return { message: 'You did it!' } } }
To protect a particular route, all we need to do is add @UseGuards(@AuthGuard()) to it and it will use our default JWT strategy to protect that route. With this in place, if we were to make a GET request to /users/test it would only work if we sent the JWT along with the request in the headers.
6. Test in an Ionic Application
With everything in place, let’s test it!
Everything we have done above really doesn’t have much to do with Ionic at all, you could use this backend with many types of frontends. In the end, all we will be doing is making GET and POST HTTP requests to the NestJS backend. Although you do not have to use Ionic, I am going to show you some rough code that you can use to test the functionality.
NOTE: The following code is purely for testing the API and is in no way designed well. Your Ionic application should not look like this. Keep in mind that you will need to have the HttpClientModule set up in order for the following code to work.
Modify src/home/home.page.ts to reflect the following:
import { Component } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'], }) export class HomePage { public createEmail: string; public createPassword: string; public signInEmail: string; public signInPassword: string; public jwt: string; constructor(private http: HttpClient){ } createAccount(){ let credentials = { email: this.createEmail, password: this.createPassword } this.http.post('http://localhost:3000/users', credentials).subscribe((res) => { console.log(res); }); } signIn(){ let credentials = { email: this.signInEmail, password: this.signInPassword } this.http.post('http://localhost:3000/auth', credentials).subscribe((res: any) => { console.log(res); // NOTE: This is just for testing, typically you would store the JWT in local storage and retrieve from there this.jwt = res.token; }); } testRoute(){ let headers = new HttpHeaders().set('Authorization', 'Bearer ' + this.jwt) this.http.get('http://localhost:3000/users/test', {headers: headers}).subscribe((res) => { console.log(res); }); } logout(){ this.jwt = null; } }
Modify src/home/home.page.html to reflect the following:
<ion-header> <ion-toolbar> <ion-title> Ionic Blank </ion-title> </ion-toolbar> </ion-header> <ion-content padding> <h2>Create Account</h2> <ion-input [(ngModel)]="createEmail" type="text" placeholder="email"></ion-input> <ion-input [(ngModel)]="createPassword" type="password" placeholder="password"></ion-input> <ion-button (click)="createAccount()" color="primary">Test Create Account</ion-button> <h2>Sign In</h2> <ion-input [(ngModel)]="signInEmail" type="text" placeholder="email"></ion-input> <ion-input [(ngModel)]="signInPassword" type="password" placeholder="password"></ion-input> <ion-button (click)="signIn()" color="primary">Test Sign In</ion-button> <ion-button (click)="testRoute()" color="light">Test Protected Route</ion-button> <ion-button (click)="logout()" color="light">Test Logout</ion-button> </ion-content>
The important part in the code above is that we are setting the Authorization header and sending our JWT as a bearer token. In order to run this example, you will need to make sure that you:
Have the MongoDB daemon running with mongod
Have your NestJS backend being served by running npm run start
Have your Ionic application served with ionic serve
If you are using the code above you should be able to go through the process of:
Creating a user
Signing in with that user
Accessing the protected route
Logging out (and you will no longer be able to access the protected route)
After creating a user or signing in, you should receive a JWT in the server response that will look something like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZ21haWwuY29tIiwiaWF0IjoxNTQ0NTcyNzM3LCJleHAiOjE1NDQ1NzYzMzd9.6p0XH9KkGsde9S38nDOkPudYk02dZK6xxtd3qqWFg3M
If you were to paste this JWT in the debugger at jwt.io you would be able to see that the payload of this JWT is:
{ "email": "[email protected]", "iat": 1544572737, "exp": 1544576337 }
However, you might notice that it says “Invalid Signature”. This is good, we should only get a valid signature when the secret key the JWT was signed with is supplied. If you were to first add the key thisismykickasssecretthatiwilltotallychangelater to the “Verify Signature” section under “your-256-bit-secret”, and then add the JWT to the “Encoded” section you will see that the signature is valid. Since this key is only known to our server, only our server can validate or create JWTs.
In the test code, we are just saving the JWT on this.jwt but you would typically save this in some kind of local storage.
If you wanted to test the security of the application you could even try modifying the values of the JWT manually, or supplying your own manually created JWTs to see if you can fool the authorisation process. After you have created a user, if you were to open a MongoDB shell in your terminal by running mongo you should be able to run the following commands:
use authexample users.find()
To retrieve a list of all of the users you have created. The users will look something like this:
{ "_id" : ObjectId("5c0dc18d349bc9479600c171"), "email" : "[email protected]", "password" : "$2b$10$2WLRRE/IWW.1yXcEyt0sZeFS/257w6SAaigbMNMfcqX1JNZ1KKXGO", "__v" : 0 }
You can see the password hashing in action here. I’ll let you in on a little secret – the test password I used for this was password. But, there is no way you could know that just by looking at the value stored in the database:
$2b$10$2WLRRE/IWW.1yXcEyt0sZeFS/257w6SAaigbMNMfcqX1JNZ1KKXGO
Since this value is hashed (not encrypted) it means that this value should not be able to be reversed. We can’t read what the password is, and neither could an attacker.
Summary
This tutorial wasn’t exactly a simple one but without too much work we now have a fully functional authentication/authorisation system that can remember users after their initial login. I’d like to stress again that this is primarily for educational purposes and has not been seriously tested – please make sure to do your own research and testing if you intend to use any of this code in a production environment.
What to watch next...
VIDEO
via joshmorony – Learn Ionic & Build Mobile Apps with Web Tech https://ift.tt/2C7R8CG
0 notes