preface
The previous article explained how to use Sequelize to connect to MySQL. Next, we will extend the original code to allow users to register and log in.
Here’s a quick mention of JWT:
JWT
JWT (JSON Web Token) is an open jSON-based standard (RFC 7519) implemented for the transfer of declarations between network application environments. The Token is designed to be compact and secure, especially suitable for single sign-on (SSO) scenarios in distributed sites. The JWT declaration is generally used to pass authenticated user identity information between the identity provider and the service provider to obtain resources from the resource server, and to add some additional declaration information necessary for other business logic. The Token can also be used directly for authentication or can be encrypted.
For details, please refer to “JSON Web Token Tutorial – Ruan Yifeng”
So the general process of JWT implementation [login] is as follows:
- Client user login request;
- The server gets the request and queries the user table according to the parameters.
- If a user is matched, the user information is issued with a visa and the Token is issued.
- The client takes the Token, stores it somewhere, and carries it with it in subsequent requests.
- After receiving the Token request, the server directly verifies the request based on the visa without querying user information.
Now, let’s start our actual combat:
GitHub project address, welcome everyone Star.
One, write encryption tool function
In the SRC directory, create a new folder utils, which will hold various utility functions, and create a new cryptogram.ts file:
import * as crypto from 'crypto';
/**
* Make salt
*/
export function makeSalt(): string {
return crypto.randomBytes(3).toString('base64'); } /** * Encrypt password * @param password Password * @param salt Password salt */export function encryptPassword(password: string, salt: string): string {
if(! password || ! salt) {return ' ';
}
const tempSalt = Buffer.from(salt, 'base64');
returnCrypto. Pbkdf2Sync (password, tempSalt, 10000, 16,'sha1').toString('base64')); }Copy the code
One is to create a random salt, and the other is to encrypt the password based on the salt.
These two functions will run through the functions of registration and login.
Second, user registration
Before writing the registration logic, we need to modify the findeOne() method in user.service.ts:
// src/logical/user/user.service.ts
import { Injectable } from '@nestjs/common';
import * as Sequelize from 'sequelize'; // Introduce the Sequelize library
import sequelize from '.. /.. /database/sequelize'; // Introduce the Sequelize instance
@Injectable(a)export class UserService {
/** * check whether the user @param username username */ exists
async findOne(username: string) :Promise<any | undefined> {
const sql = `
SELECT
user_id userId, account_name username, real_name realName, passwd password,
passwd_salt salt, mobile, role
FROM
admin_user
WHERE
account_name = '${username}'`; // A plain SQL query
try {
const user = (await sequelize.query(sql, {
type: Sequelize.QueryTypes.SELECT, // Query mode
raw: true.// Whether to display the result as an array assembly
logging: true.// Whether to print SQL statements to the console}))0];
// If no user is found, user === undefined
return user;
} catch (error) {
console.error(error);
return void 0; }}}Copy the code
FindOne () now works better with its method name, returning user information if found, undefined if not found.
Next, let’s start writing the registration function:
// src/logical/user/user.service.ts
import { Injectable } from '@nestjs/common';
import * as Sequelize from 'sequelize'; // Introduce the Sequelize library
import sequelize from '.. /.. /database/sequelize'; // Introduce the Sequelize instance
import { makeSalt, encryptPassword } from '.. /.. /utils/cryptogram'; // Introduce the encryption function
@Injectable(a)export class UserService {
/** * check whether the user @param username username */ exists
async findOne(username: string) :Promise<any | undefined> {... }/** * register * @param requestBody */
async register(requestBody: any) :Promise<any> {
const { accountName, realName, password, repassword, mobile } = requestBody;
if(password ! == repassword) {return {
code: 400,
msg: 'Two different passwords entered'}; }const user = await this.findOne(accountName);
if (user) {
return {
code: 400,
msg: 'User already exists'}; }const salt = makeSalt(); // Make password salt
const hashPwd = encryptPassword(password, salt); // Encrypt the password
const registerSQL = `
INSERT INTO admin_user
(account_name, real_name, passwd, passwd_salt, mobile, user_status, role, create_by)
VALUES
('${accountName}', '${realName}', '${hashPwd}', '${salt}', '${mobile}', 1, 3, 0) ';
try {
await sequelize.query(registerSQL, { logging: false });
return {
code: 200,
msg: 'Success'}; }catch (error) {
return {
code: 503,
msg: `Service error: ${error}`}; }}}Copy the code
Once written, add routes in user.controller.ts
// src/logical/user/user.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly usersService: UserService) {}
// @Post('find-one')
// findOne(@Body() body: any) {
// return this.usersService.findOne(body.username);
// }
@Post('register')
async register(@Body() body: any) {
return await this.usersService.register(body); }}Copy the code
Now, let’s use Postman to test this by deliberately entering a different password and an existing username:
As shown, the inconsistent password verification is triggered.
Then, we change the password to the same:
As shown, the verification of the existing user is triggered.
Then, we enter the correct parameters:
Let’s go to the database again:
The information has been inserted into the table, and the password is encrypted, so far, the registration function is basically complete.
Iii. Configuration and verification of JWT
In order to feel the processing order more intuitively, I added step printing to the code
1. Install dependency packages
$ yarn add passport passport-jwt passport-local @nestjs/passport @nestjs/jwt -S
Copy the code
2. Create the Auth module
$ nest g service auth logical
$ nest g module auth logical
Copy the code
3. Create a file to store constants
Add a constants.ts to the auth folder to store the various constants used:
// src/logical/auth/constats.ts
export const jwtConstants = {
secret: 'shinobi7414' / / the secret key
};
Copy the code
4. Write JWT policies
Add a new jwt.strategy.ts to the auth folder to write JWT validation strategy:
// src/logical/auth/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';
@Injectable(a)export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
// JWT validation -step 4: called by the guard
async validate(payload: any) {
console.log('JWT validation - Step 4: called by the guard');
return{ userId: payload.sub, username: payload.username, realName: payload.realName, role: payload.role }; }}Copy the code
5. Write auth.service.ts validation logic
// src/logical/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { UserService } from '.. /user/user.service';
import { JwtService } from '@nestjs/jwt';
import { encryptPassword } from '.. /.. /utils/cryptogram';
@Injectable(a)export class AuthService {
constructor(private readonly usersService: UserService, private readonly jwtService: JwtService) {}
// JWT authentication-step 2: verifies user information
async validateUser(username: string, password: string) :Promise<any> {
console.log('JWT Validation - Step 2: Verify user information ');
const user = await this.usersService.findOne(username);
if (user) {
const hashedPassword = user.password;
const salt = user.salt;
// Use the password salt to encrypt the parameter, and then compare it with the database to determine whether it is equal
const hashPassword = encryptPassword(password, salt);
if (hashedPassword === hashPassword) {
// Correct password
return {
code: 1,
user,
};
} else {
// The password is incorrect
return {
code: 2,
user: null}; }}// There is no such person
return {
code: 3,
user: null}; }// JWT validation - Step 3: Process JWT visa
async certificate(user: any) {
const payload = { username: user.username, sub: user.userId, realName: user.realName, role: user.role };
console.log('JWT Validation - Step 3: Process JWT visa ');
try {
const token = this.jwtService.sign(payload);
return {
code: 200,
data: {
token,
},
msg: 'Login succeeded'}; }catch (error) {
return {
code: 600,
msg: 'Wrong account or password'}; }}}Copy the code
When you save the file, the console displays an error:
We can leave it as it is not yet associated with JwtService and UserService in auth.module.ts.
5. Write local policies
This step is optional and depends on the needs of the project to determine whether a local policy is needed
// src/logical/auth/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable(a)export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super(a); }async validate(username: string, password: string) :Promise<any> {
const user = await this.authService.validateUser(username, password);
if(! user) {throw new UnauthorizedException();
}
returnuser; }}Copy the code
6. The Module
// src/logical/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { UserModule } from '.. /user/user.module';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '8h' }, // Token expires
}),
UserModule,
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
Copy the code
If save the file, and the above error at this time, you need to go to the app. The module. The ts, removes AuthService from will array, and add in the imports array AuthModule can:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './logical/user/user.module';
// import { AuthService } from './logical/auth/auth.service';
import { AuthModule } from './logical/auth/auth.module';
@Module({
imports: [UserModule, AuthModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Copy the code
7. Write a Login route
At this point, returning to user.controller.ts, we import the assembled JWT related files and determine the user status based on the verification code:
// src/logical/user/user.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from '.. /auth/auth.service';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly authService: AuthService, private readonly usersService: UserService) {}
// JWT authentication -step 1: user requests login
@Post('login')
async login(@Body() loginParmas: any) {
console.log('JWT Authentication - Step 1: User requests login ');
const authResult = await this.authService.validateUser(loginParmas.username, loginParmas.password);
switch (authResult.code) {
case 1:
return this.authService.certificate(authResult.user);
case 2:
return {
code: 600,
msg: Incorrect account or password};default:
return {
code: 600,
msg: 'No such person'}; }}@Post('register')
async register(@Body() body: any) {
return await this.usersService.register(body); }}Copy the code
The same error occurs when the file is saved:
This time we will go to user.module.ts and comment out the controllers:
We need to go to app.module.ts and add Controller back:
This is because if you add AuthService to user.module.ts, you will need to introduce other policies again. I feel very troublesome, so I simply use the app to unify management.
4. Login verification
Now that we have a list of code, it is time to check the effect, we will use the original registered information, the login request:
As you can see, a long list of tokens have been returned, and the console has printed the login steps and user information. With this token, the front end can request other guarded interfaces.
Let’s try typing in the wrong account or password:
Five, the guards
Now that you have issued the Token, you need to be able to verify the Token, so Guard is used.
Add @useGuards (AuthGuard(‘ JWT ‘)) to the route:
// src/logical/user/user.controller.ts
import { Controller, Post, Body, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '.. /auth/auth.service';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly authService: AuthService, private readonly usersService: UserService) {}
@Post('login')
async login(@Body() loginParmas: any) {... }@UseGuards(AuthGuard('jwt')) // Use 'JWT' for validation
@Post('register')
async register(@Body() body: any) {
return await this.usersService.register(body); }}Copy the code
Then, let’s try the request without token in the header:
As you can see, return the 401 status code for Unauthorized access.
Now, let’s try the Token case, copying the login tokens into the Postman authorship (select Bearer tokens) :
Then request the interface:
At this point, you are ready to access it. Look at the console print and the steps are as commented in the code:
At this point, the login function is basically complete.
conclusion
This article describes how to use JWT to issue tokens for user logins and how to verify user information when a Token request is received.
Of course, implementing login authentication is not limited to JWT, and there are many other methods that interested readers can explore for themselves.
The disadvantages of JWT are that it cannot be used to log in with the same account, and the later login can be used to edge out the earlier login, that is, to invalidate the previous Token, so as to ensure information security (at least I did not find the relevant solution, if there is a big god to solve this problem, please advise). Tokens can only be squeezed out using some other black tech (e.g. Redis).
Now that you have the registration and login functions, it’s time to refine the other common functions that a server should have.
The next article covers interceptors, exception handling, and log collection.
This article is included in the NestJS practical tutorial, more articles please pay attention to.
`