A production-ready authentication microservice built with the Fruster framework. Provides JWT-based authentication with refresh tokens, password management, and user registration.
- User registration with email and username
- Login with JWT access tokens
- Refresh token rotation for secure token renewal
- Password change with validation
- Token validation endpoint
- Password strength requirements
- Email format validation
- MongoDB storage with proper indexing
- Comprehensive test suite
This service follows Fruster best practices:
- Handlers: Message handlers for NATS subjects (both service-to-service and HTTP)
- Managers: Business logic layer (AuthManager, TokenManager)
- Repositories: Data access layer (UserRepository)
- Models: TypeScript interfaces with JSDoc for schema generation
- Node.js 18+
- NATS server
- MongoDB
npm installCreate a .env file or set environment variables:
# NATS Configuration
NATS_URL=nats://localhost:4222
# MongoDB Configuration
MONGO_URL=mongodb://localhost:27017
MONGO_DB_NAME=auth-service
# JWT Configuration
JWT_SECRET=your-secret-key-change-in-production
JWT_EXPIRES_IN=7d
JWT_REFRESH_EXPIRES_IN=30d
# Bcrypt Configuration
BCRYPT_SALT_ROUNDS=10# Development mode
npm start
# Development with build
npm run start:build
# Build only
npm run build# Run all tests
npm test
# Run tests in watch mode
npm run test:watchSubject: auth-service.register or http.post.auth.register
Request:
{
email: string; // Valid email format
username: string; // 3-30 chars, alphanumeric + _ -
password: string; // Min 8 chars, requires uppercase, lowercase, number, special char
name?: string; // Optional full name
roles?: string[]; // Optional roles (defaults to ["user"])
}Response: 201 Created
{
user: User;
tokens: {
accessToken: string;
refreshToken: string;
expiresIn: number;
}
}Subject: auth-service.login or http.post.auth.login
Request:
{
email: string;
password: string;
}Response: 200 OK
{
user: User;
tokens: {
accessToken: string;
refreshToken: string;
expiresIn: number;
}
}Subject: auth-service.validate-token or http.post.auth.validate
Request:
{
token: string; // JWT access token
}Response: 200 OK
{
valid: boolean;
payload?: {
userId: string;
email: string;
username: string;
roles?: string[];
}
}Subject: auth-service.refresh-token or http.post.auth.refresh
Request:
{
refreshToken: string;
}Response: 200 OK
{
tokens: {
accessToken: string;
refreshToken: string;
expiresIn: number;
}
}Subject: auth-service.change-password or http.post.auth.change-password
Request:
{
userId: string;
oldPassword: string;
newPassword: string; // Min 8 chars, requires uppercase, lowercase, number, special char
}Response: 200 OK
{
success: boolean;
message: string;
}Subject: auth-service.get-user or http.get.auth.user.:userId
Request:
{
userId: string;
}Response: 200 OK
{
id: string;
email: string;
username: string;
name?: string;
roles?: string[];
isActive: boolean;
createdAt: string;
updatedAt: string;
}USER_ALREADY_EXISTS(409): Email or username already registeredINVALID_CREDENTIALS(401): Wrong email or passwordUSER_NOT_FOUND(404): User does not existINVALID_TOKEN(401): Token is invalid or expiredTOKEN_EXPIRED(401): Token has expiredREFRESH_TOKEN_NOT_FOUND(404): Refresh token not found or revokedWEAK_PASSWORD(400): Password doesn't meet strength requirementsINVALID_EMAIL_FORMAT(400): Email format is invalid
- Passwords hashed with bcrypt (configurable salt rounds)
- JWT tokens with configurable expiration
- Refresh token rotation (old tokens revoked after refresh)
- All refresh tokens revoked on password change
- Automatic cleanup of expired tokens via MongoDB TTL index
- Email and username normalized to lowercase
- Strong password validation
{
_id: string; // UUID
email: string; // Unique, lowercase
username: string; // Unique, lowercase
passwordHash: string; // Bcrypt hash
name?: string;
roles?: string[];
isActive: boolean;
createdAt: string; // ISO date-time
updatedAt: string; // ISO date-time
metadata?: object;
}Indexes:
email: uniqueusername: unique
{
_id: string; // UUID
token: string; // UUID
userId: string; // Reference to user
expiresAt: Date;
createdAt: Date;
isRevoked: boolean;
}Indexes:
token: uniqueuserId: non-uniqueexpiresAt: TTL index (auto-delete expired tokens)
fruster-auth-service/
├── app.ts # Entry point
├── config.ts # Configuration
├── auth-service.ts # Service initialization
├── lib/
│ ├── errors.ts # Custom error definitions
│ ├── models/ # TypeScript interfaces
│ │ ├── User.ts
│ │ └── Token.ts
│ ├── repositories/ # Data access layer
│ │ └── UserRepository.ts
│ ├── managers/ # Business logic
│ │ ├── AuthManager.ts
│ │ └── TokenManager.ts
│ └── handlers/ # Message handlers
│ ├── RegisterUserHandler.ts
│ ├── LoginHandler.ts
│ ├── ValidateTokenHandler.ts
│ ├── RefreshTokenHandler.ts
│ ├── ChangePasswordHandler.ts
│ └── GetUserByIdHandler.ts
└── spec/ # Tests
├── helpers/
│ └── jasmine.ts
├── RegisterUserHandler.spec.ts
├── LoginHandler.spec.ts
├── ValidateTokenHandler.spec.ts
├── RefreshTokenHandler.spec.ts
└── ChangePasswordHandler.spec.ts
- Create handler file in
lib/handlers/ - Define request/response interfaces with JSDoc
- Use
@injectable()and@subscribe()decorators - Inject dependencies with
@inject() - Register handler in
auth-service.ts - Write tests in
spec/
# Lint code
npm run lint
# Auto-fix linting issues
npm run lint:fixISC