Implementing Secure Authentication with JWT in Full-Stack Applications

 

In today’s digital age, secure authentication is a cornerstone of modern web applications. One of the most popular methods for implementing authentication is JSON Web Tokens (JWT), which offers a secure, stateless, and scalable way to manage user sessions.

In this article, I’ll guide you through implementing secure authentication with JWT in a full-stack application using NestJS and showcase how to protect your backend APIs effectively.

What is JWT?

JWT (JSON Web Token) is an open standard for securely transmitting information as a JSON object. A JWT typically has three parts:

1. Header

The header contains metadata about the token, such as:

  • Type of token (e.g., “JWT”).
  • Signing algorithm used (e.g., HMAC SHA256 or RSA).

Example:

{
"alg": "HS256",
"typ": "JWT"
}

2. Payload

The payload contains the claims, which are statements about an entity (typically the user) and additional data. Claims can be:

  • Registered claims: Standardized claims like sub (subject), iat (issued at), exp (expiration time), etc.
  • Public claims: Custom claims agreed upon by parties.
  • Private claims: Custom claims are used internally within an organization.

Example:

{
"sub": "1234567890",
"name": "Ali Hamza",
"admin": true
}

3. Signature

The signature ensures the token’s integrity and authenticity. It is created by combining the encoded header, encoded payload, and a secret or private key, and then signing them using the specified algorithm.

Example (for HMAC SHA256):

HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)

Complete JWT Example

A JWT is typically represented as three Base64Url-encoded strings separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaSBIYW16YSIsImFkbWluIjp0cnVlfQ.op-HRNt47KP7uUDw-GA_cIDMc3KWSWJlL_prtzGkE-c

Steps to Implement JWT Authentication in NestJS

1. Set Up Your NestJS Application

Start by creating a new NestJS project if you don’t already have one.

# Install NestJS CLI if you haven’t already
npm i -g @nestjs/cli

# Create a new project
nest new jwt-auth-demo

# Navigate into the project directory
cd jwt-auth-demo

2. Install Required Dependencies

We need the following dependencies for JWT implementation:

  • @nestjs/jwt: Provides utilities for working with JWT in NestJS.
  • @nestjs/passport and passport-jwt: Facilitate JWT-based authentication.
  • bcryptjs: Hashes passwords securely.
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcryptjs @types/bcryptjs

3. Create Authentication Module

Generate a new module to handle authentication.

nest g module auth  
nest g service auth
nest g controller auth

You can also generate modules with a single command using nest g resource “auth”

4. Implement JWT Authentication

4.1 Create a User Entity

For this example, let’s assume we have a User a model with fields like id, email, and password.

// src/auth/user.entity.ts
export class User {
id: number;
email: string;
password: string; // Hashed password
}

4.2 Create a JWT Strategy

The JWT Strategy validates and decodes tokens.

// src/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'your_secret_key', // Use environment variables in production
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}

Register this strategy in your AuthModule.

// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';

@Module({
imports: [
PassportModule,
JwtModule.register({
secret: 'your_secret_key', // Use environment variables in production
signOptions: { expiresIn: '1h' },
}),
],
providers: [AuthService, JwtStrategy],
controllers: [AuthController],
})
export class AuthModule {}

4.3 Create the Authentication Service

The service will handle user validation, token generation, and hashing passwords.

// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcryptjs';
import { User } from './user.entity';

@Injectable()
export class AuthService {
private users: User[] = []; // Replace with database integration
constructor(private jwtService: JwtService) {}

async register(email: string, password: string): Promise<User> {
const hashedPassword = await bcrypt.hash(password, 10);
const user: User = { id: Date.now(), email, password: hashedPassword };
this.users.push(user);
return user;
}

async validateUser(email: string, password: string): Promise<User | null> {
const user = this.users.find((u) => u.email === email);
if (user && (await bcrypt.compare(password, user.password))) {
return user;
}
return null;
}

async login(user: User) {
const payload = { sub: user.id, email: user.email };
return { accessToken: this.jwtService.sign(payload) };
}
}

4.4 Create the Authentication Controller

The controller exposes endpoints for registration and login.

// src/auth/auth.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}

@Post('register')
async register(
@Body('email') email: string,
@Body('password') password: string,
) {
return this.authService.register(email, password);
}

@Post('login')
async login(
@Body('email') email: string,
@Body('password') password: string,
) {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new Error('Invalid credentials');
}
return this.authService.login(user);
}
}

5. Protect Routes Using Guards

Use a guard to secure specific routes.

// src/auth/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

Apply the guard to your protected routes.

// src/app.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './auth/jwt-auth.guard';

@Controller('profile')
export class AppController {
@UseGuards(JwtAuthGuard)
@Get()
getProfile() {
return { message: 'This is a protected route' };
}
}

Testing the Implementation

1. Register a User

Send a POST request to /auth/register with an email and password.

{
"email": "test@example.com",
"password": "securepassword"
}

2. Login

Send a POST request to /auth/login with the same credentials. The response will include a JWT.

{
"accessToken": "your.jwt.token"
}

3. Access Protected Route

Use the JWT as a Bearer token in the Authorization header to access /profile.

Conclusion

With JWT, you can implement secure, stateless authentication in your applications, ensuring a better user experience and scalability. This example provides a solid foundation for integrating JWT in NestJS, and you can further extend it with features like token refresh, role-based access control, and database integration.

Let me know your thoughts or questions in the comments!

Follow Me:

#NestJS #JWTAuthentication #WebDevelopment #FullStackDevelopment #BackendDevelopment #NodeJS #WebSecurity #AuthModule #SecureAuthentication #APIDevelopment #JavaScript #TypeScript #DevCommunity #CodeExample #LearnWithMe #ProgrammingTutorial #DeveloperTips #TechInnovation #CodeWithMe #AliHamza #SyedAliHamzaZaidi

Happy Learning 😊

No comments:

Post a Comment

Pages