Implementing OAuth 2.0 with JWT Authentication in a NestJS API
In the world of web development, security is paramount. When building APIs, especially those that handle sensitive user data, implementing a robust authentication mechanism is essential. One of the most popular methods today is using OAuth 2.0 combined with JSON Web Tokens (JWT). This article will guide you through implementing OAuth 2.0 with JWT authentication in a NestJS API, providing clear code examples and actionable insights.
Understanding OAuth 2.0 and JWT
What is OAuth 2.0?
OAuth 2.0 is an authorization framework that allows third-party services to exchange limited access rights to user accounts without exposing user credentials. It’s widely used for scenarios where users need to grant applications access to their information stored on another service (like Google or Facebook) without sharing their passwords.
What is JWT?
JSON Web Token (JWT) is an open standard for securely transmitting information between parties as a JSON object. It is compact, URL-safe, and can be verified and trusted because it is digitally signed. In the context of OAuth, JWTs are used as access tokens.
Use Cases
- Single Sign-On (SSO): Users can log in to multiple applications with one set of credentials.
- Mobile Applications: Secure access to backend services from mobile devices.
- Third-party Integrations: Allowing applications to access user data without compromising security.
Setting Up Your NestJS Project
Step 1: Create a New NestJS Project
If you haven’t already, install the NestJS CLI and create a new project:
npm i -g @nestjs/cli
nest new oauth-jwt-example
cd oauth-jwt-example
Step 2: Install Required Packages
You’ll need several packages for implementing OAuth 2.0 and JWT authentication:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install --save-dev @types/passport-jwt
Implementing JWT Authentication
Step 3: Create the Auth Module
Generate an auth module and service:
nest generate module auth
nest generate service auth
Step 4: Create User Module
For simplicity, we’ll create a user module that simulates user data.
nest generate module users
nest generate service users
Step 5: User Service Implementation
In users.service.ts
, you can simulate user data:
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private readonly users = [{ username: 'john', password: 'changeme' }];
async findOne(username: string) {
return this.users.find(user => user.username === username);
}
}
Step 6: Auth Service Implementation
In auth.service.ts
, implement the authentication logic:
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(private usersService: UsersService, private jwtService: JwtService) {}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.usersService.findOne(username);
if (user && user.password === password) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { username: user.username };
return {
access_token: this.jwtService.sign(payload),
};
}
}
Step 7: Setting Up JWT Strategy
Create a strategy for JWT authentication:
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private usersService: UsersService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'your_jwt_secret', // Use a more secure secret in production
});
}
async validate(payload: any) {
return this.usersService.findOne(payload.username);
}
}
Step 8: Configure the Auth Module
In auth.module.ts
, set up the JWT module and the strategy:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule, JwtModule.register({
secret: 'your_jwt_secret',
signOptions: { expiresIn: '60s' }, // Token expiration time
})],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
Step 9: Create Auth Controller
Create a controller to handle login requests:
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('login')
async login(@Body() user: { username: string; password: string }) {
return this.authService.login(user);
}
}
Step 10: Protect Routes with JWT
To secure your routes, use the @UseGuards
decorator with JwtAuthGuard
:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './jwt-auth.guard';
@Controller('protected')
export class ProtectedController {
@UseGuards(JwtAuthGuard)
@Get()
getProtectedResource() {
return { message: 'This is a protected resource' };
}
}
Step 11: Testing the API
You can now test your API using tools like Postman:
-
Login: Send a POST request to
/auth/login
with JSON body:json { "username": "john", "password": "changeme" }
You should receive a JWT token in response. -
Access Protected Route: Use the retrieved JWT token to make a GET request to
/protected
, including it in the Authorization header as a Bearer token.
Conclusion
Implementing OAuth 2.0 with JWT authentication in a NestJS API is a powerful way to secure your application. This setup enhances user experience by allowing single sign-on capabilities while ensuring that sensitive information remains protected.
By following the steps outlined in this article, you can quickly set up a robust authentication mechanism in your NestJS application. Remember to always use secure practices when handling user data and tokens in production environments. Happy coding!