@katalyst/auth
A flexible and feature-rich authentication module for NestJS applications with support for JWT tokens, refresh tokens, multiple sessions, and various storage options.
cd existing_repo git remote add origin https://ntgitlab.novatechset.com/katalysttech-eco-system/auth-package.git git branch -M main git push -uf origin main
Features
- 🔐 JWT-based authentication
- 🔄 Refresh token support
- 📱 Multiple device sessions
- 💾 TypeORM and Redis storage support
- 🔒 Password hashing utilities
- 👥 Session management
- 🛡️ Route protection
- ⚙️ Highly configurable
Installation
npm install @katalyst/auth
Dependencies
Based on your storage choice, install the required dependencies:
For TypeORM storage:
npm install typeorm @nestjs/typeorm
For Redis storage:
npm install ioredis
Configuration
1. Module Setup
Synchronous Configuration
import { Module } from '@nestjs/common';
import { AuthModule } from '@katalyst/auth';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RefreshToken } from './entities/refresh-token.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'your_username',
password: 'your_password',
database: 'your_database',
entities: [RefreshToken],
synchronize: true, // set to false in production
}),
AuthModule.register({
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: '15m',
},
refreshToken: {
secret: process.env.REFRESH_TOKEN_SECRET,
expiresIn: '7d',
maxActiveSessions: 5, // Optional: limit active sessions per user
storage: {
type: 'typeorm',
entity: RefreshToken,
}
},
findUserById: async (id) => {
// Implement your user lookup logic
return await userRepository.findOne({ where: { id } });
},
findUserByUsername: async (username) => {
// Implement your user lookup logic
return await userRepository.findOne({ where: { username } });
},
userFields: {
idField: 'id',
usernameField: 'username',
passwordField: 'password',
tokenFields: ['role'] // Additional fields to include in JWT payload
}
}),
],
})
export class AppModule {}
Async Configuration
import { Module } from '@nestjs/common';
import { AuthModule } from '@katalyst/auth';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot(),
AuthModule.registerAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
jwt: {
secret: configService.get('JWT_SECRET'),
expiresIn: configService.get('JWT_EXPIRES_IN'),
},
refreshToken: {
secret: configService.get('REFRESH_TOKEN_SECRET'),
expiresIn: configService.get('REFRESH_TOKEN_EXPIRES_IN'),
maxActiveSessions: configService.get('MAX_ACTIVE_SESSIONS'),
storage: {
type: 'typeorm',
entity: RefreshToken,
}
},
// ... other config
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
2. Entity Setup (For TypeORM)
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity('refresh_tokens')
export class RefreshToken {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
userId: string;
@Column()
tokenId: string;
@Column()
expiresAt: Date;
@Column({ default: false })
isRevoked: boolean;
@CreateDateColumn()
createdAt: Date;
@Column('json', { nullable: true })
deviceInfo: any;
}
Important Notes About Entity Setup
- The
id
field must be of typestring
to match the package's requirements - Use
@PrimaryGeneratedColumn('uuid')
for string IDs - If you need to use numeric IDs, you'll need to convert them to strings in your entity
Example with Numeric ID (if required)
@Entity('refresh_tokens')
export class RefreshToken {
@PrimaryGeneratedColumn('increment')
@Transform(({ value }) => value.toString())
id: string;
// ... other fields
}
Troubleshooting
Common Issues
ID Type Mismatch If you see the error: "The types of '(new refreshToken.storage.entity(...)).id' are incompatible between these types. Type 'number' is not assignable to type 'string'", you need to ensure your RefreshToken entity's
id
field is of typestring
. See the Entity Setup section above for solutions.Storage Configuration When using TypeORM storage, make sure to:
- Pass the correct DataSource instance
- Use the correct entity type
- Configure the proper ID field type
Usage
1. Authentication Controller
import { Controller, Post, Body, Get, UseGuards, Request } from '@nestjs/common';
import { AuthService, AuthGuard } from '@katalyst/auth';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
async login(@Body() credentials: { username: string; password: string }) {
return await this.authService.login(credentials.username, credentials.password);
}
@Post('refresh')
async refresh(@Body() body: { refreshToken: string }) {
return await this.authService.refreshToken(body.refreshToken);
}
@Get('sessions')
@UseGuards(AuthGuard)
async getSessions(@Request() req: any) {
return await this.authService.getActiveSessions(req.user.sub);
}
@Post('logout')
@UseGuards(AuthGuard)
async logout(@Request() req: any) {
await this.authService.logout(req.user.sub, req.user.tokenId);
return { message: 'Logged out successfully' };
}
@Post('logout-all')
@UseGuards(AuthGuard)
async logoutAll(@Request() req: any) {
await this.authService.logoutAll(req.user.sub);
return { message: 'Logged out from all devices' };
}
}
2. Protecting Routes
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@katalyst/auth';
@Controller('protected')
@UseGuards(AuthGuard)
export class ProtectedController {
@Get()
getProtectedData() {
return { message: 'This is protected data' };
}
}
3. Password Utilities
import { Injectable } from '@nestjs/common';
import { PasswordService } from '@katalyst/auth';
@Injectable()
export class UserService {
constructor(private readonly passwordService: PasswordService) {}
async createUser(username: string, password: string) {
const hashedPassword = await this.passwordService.hashPassword(password);
// Save user with hashed password
}
async validatePassword(plainPassword: string, hashedPassword: string) {
return await this.passwordService.comparePassword(plainPassword, hashedPassword);
}
}
4. Session Management
import { Controller, Get, Post, UseGuards, Request, Param } from '@nestjs/common';
import { AuthService, AuthGuard } from '@katalyst/auth';
@Controller('sessions')
@UseGuards(AuthGuard)
export class SessionController {
constructor(private readonly authService: AuthService) {}
@Get()
async getActiveSessions(@Request() req: any) {
return await this.authService.getActiveSessions(req.user.sub);
}
@Post('revoke/:tokenId')
async revokeSession(@Request() req: any, @Param('tokenId') tokenId: string) {
await this.authService.revokeToken(req.user.sub, tokenId);
return { message: 'Session revoked successfully' };
}
}
Environment Variables
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRES_IN=15m
REFRESH_TOKEN_SECRET=your_refresh_token_secret
REFRESH_TOKEN_EXPIRES_IN=7d
MAX_ACTIVE_SESSIONS=5
API Response Types
Login Response
interface AuthTokens {
accessToken: string;
refreshToken: string;
expiresIn: number;
}
Active Session Response
interface Session {
id: string;
createdAt: Date;
expiresAt: Date;
deviceInfo?: {
userAgent?: string;
ip?: string;
// ... other device info
};
}
Error Handling
The module throws standard NestJS exceptions:
UnauthorizedException
: Invalid credentials, invalid token, or revoked tokenBadRequestException
: Invalid refresh token or missing required fieldsForbiddenException
: Token revoked or session limit exceeded
Best Practices
Security
- Store secrets in environment variables
- Use strong secret keys
- Set appropriate token expiration times
- Implement rate limiting for auth endpoints
Token Storage
- Use secure storage for refresh tokens
- Implement regular token cleanup
- Monitor active sessions
Error Handling
- Implement proper error handling in your controllers
- Log authentication failures
- Provide clear error messages to users
Contributing
Contributions are welcome! Please read our contributing guidelines for details.
License
This project is licensed under the MIT License - see the LICENSE file for details.