Before diving into the implementation, it's essential to understand the structure and functionality of JWTs. A JWT consists of three parts:
JWT) and the signing algorithm being used, such as HMAC SHA256.exp for expiration time) and custom claims (custom fields like user roles).JWTs are typically used in scenarios where you need to securely transfer information, such as authenticating users in a web application. The token is usually stored client-side, like in a browser's local storage, and sent with each request to the server via a Bearer token in the request header.
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { UserModule } from '../user/user.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
@Module({
imports: [
UserModule,
JwtModule.register({
global: true,
secret: jwtConstants.secret,
signOptions: { expiresIn: '60s' },
}),
],
providers: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { UserService } from '../user/user.service';
const SALT_ROUNDS = 10;
export type AuthResponse = {
access_token: string;
};
@Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService,
) {}
async register(username: string, unhashedPassword: string): Promise<string> {
const password = await bcrypt.hash(unhashedPassword, SALT_ROUNDS);
const user = await this.userService.create(username, password);
return user.username;
}
async logIn(username: string, unhashedPassword: string): Promise<AuthResponse | null> {
const user = await this.userService.findByUsername(username);
if (!user || !(await bcrypt.compare(unhashedPassword, user.password))) {
return null;
}
const payload = { sub: user._id, username: user.username };
return {
access_token: await this.jwtService.signAsync(payload),
};
}
}AuthController// src/auth/auth.controller.ts
import { Body, Controller, Post, HttpException, HttpStatus } from '@nestjs/common';
import { UsernameWithPassword } from './dtos/auth.dto';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('register')
async register(@Body() createUserDto: UsernameWithPassword) {
return await this.authService.register(
createUserDto.username,
createUserDto.password,
);
}
@Post('login')
async login(@Body() loginUserDto: UsernameWithPassword) {
const token = await this.authService.logIn(
createUserDto.username,
createUserDto.password,
);
if (!token) {
throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED);
}
return token;
}
}// send_request.ts
import axios from 'axios';
// Register a new user
async function registerUser(username: string) {
try {
const response = await axios.post('http://localhost:3000/auth/register', {
username,
password: 'testpass',
});
return response.data;
} catch (error: any) {
console.error('Error:', error.message);
}
}
// Log in a user
async function loginUser(username: string) {
try {
const response = await axios.post('http://localhost:3000/auth/login', {
username,
password: 'testpass',
});
return response.data;
} catch (error: any) {
console.error('Error:', error.message);
}
}
async function run() {
console.log('Registering a new user');
await registerUser("testuser");
console.log('\nLogging in the user');
const { access_token } = await loginUser("testuser1");
console.log("Received JWT: ", access_token);
}
run();import axios from 'axios';
axios.get('http://localhost:3000/todos', {
headers: { Authorization: `Bearer ${token}` }
});