@concepta/nestjs-auth-local
TypeScript icon, indicating that this package has built-in type declarations

4.0.0 • Public • Published

Rockets NestJS Local Authentication

Authenticate requests using username/email and password against a local or remote data source.

Project

NPM Latest NPM Downloads GH Last Commit GH Contrib NestJS Dep

Table of Contents

Tutorials

1. Getting Started with AuthLocalModule

1.1 Introduction

Overview of the Library

The AuthLocalModule is a robust NestJS module designed for implementing local authentication using username and password. This module leverages the passport-local strategy to authenticate users locally within your application.

Purpose and Key Features
  • Local Authentication: Provides a straightforward way to implement local authentication using username and password.

  • Synchronous and Asynchronous Registration: Flexibly register the module either synchronously or asynchronously, depending on your application's needs.

  • Global and Feature-Specific Registration: Use the module globally across your application or tailor it for specific features.

  • Customizable: Easily customize various aspects such as user validation, token issuance, and password validation.

1.2 Installation

Install the AuthLocalModule package:
npm install class-transformer
npm install class-validator
npm install @concepta/ts-core
npm install @concepta/nestjs-authentication
npm install @concepta/nestjs-password
npm install @concepta/nestjs-jwt
npm install @concepta/nestjs-auth-local

or

yarn add class-transformer
yarn add class-validator
yarn add @concepta/ts-core
yarn add @concepta/nestjs-authentication
yarn add @concepta/nestjs-password
yarn add @concepta/nestjs-jwt
yarn add @concepta/nestjs-auth-local
Add the AuthLocalModule to Your NestJS Application:

Import the AuthLocalModule and required services in your application module. Ensure to provide the necessary configuration options at AuthLocalOptionsInterface.

The AuthLocalOptionsInterface defines the configuration options for the local authentication strategy within a NestJS application using the @concepta/nestjs-auth-local package. This interface allows for the customization of userLookupService, issueTokenService, validateUserService, and passwordValidationService. Please see Reference for more details.

Optional fields utilize default implementations, enabling straightforward integration and flexibility to override with custom implementations as needed. This setup ensures that developers can tailor the authentication process to specific requirements while maintaining a robust and secure authentication framework.

1.3 Basic Setup in a NestJS Project

Scenario: Users can log in using local authentication

To test this scenario, we will set up an application where users can log in using a username and password. We will create the necessary entities, services, module configurations.

Note: The @concepta/nestjs-user module can be used in place of our example User related prerequisites.

Step 1: Create Entities

First, create the User entity.

// user.entity.ts
export class User {
  id: number;
  username: string;
  password: string;
}

Step 2: Create Services

Next, you need to create the UserLookupService. This service is responsible for the business logic related to retrieving user data. It should implement the AuthLocalUserLookupServiceInterface.

Within this service, implement the byUsername method to fetch user details by their username (or email). Ensure that the method returns a User object containing passwordHash and passwordSalt.

These attributes are crucial as they are used by the validateUser method in the passwordValidationService to authenticate the user, which is a configurable option in the AuthLocalModule.

// user-lookup.service.ts
import { Injectable } from '@nestjs/common';
import { ReferenceUsername } from '@concepta/ts-core';
import { AuthLocalUserLookupServiceInterface } from '@concepta/nestjs-auth-local';
import { AuthLocalCredentialsInterface } from '@concepta/nestjs-auth-local/dist/interfaces/auth-local-credentials.interface';

@Injectable()
export class UserLookupService implements AuthLocalUserLookupServiceInterface {
  async byUsername(
    username: ReferenceUsername,
  ): Promise<AuthLocalCredentialsInterface | null> {
    // make sure this method will return a valid user with
    // correct passwordHash and passwordSalt
    // let's user this mock data for the purposes of this tutorial
    return {
      id: '5b3f5fd3-9426-4c4d-a06d-b4d55079034d',
      username: username,
      passwordHash:
        '$2b$12$9rQ4qZx8gpTaTR4ic3LQ.OkebyVBa48DP42jErL1zfqF17WeG4hHC',
      passwordSalt: '$2b$12$9rQ4qZx8gpTaTR4ic3LQ.O',
      active: true,
    };
  }
}

Step 3: Configure the Module

Configure the module to include the necessary services userLookupService.

// app.module.ts
import { Module } from '@nestjs/common';
import { AuthLocalModule } from '@concepta/nestjs-auth-local';
import { JwtModule } from '@concepta/nestjs-jwt';
import { UserLookupService } from './user-lookup.service';

@Module({
  imports: [
    JwtModule.forRoot({}),
    AuthLocalModule.forRoot({
      userLookupService: new UserLookupService(),
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

Validating the Setup

To validate the setup, you can use curl commands to simulate frontend requests. Here are the steps to test the login endpoint:

Step 1: Obtain a JWT Token

Assuming you have an endpoint to obtain a JWT token, use curl to get the token. Replace auth-url with your actual authentication URL, and username and password with valid credentials.

curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "testuser", "password": "testpassword"}'

This should return a response with a login message.

Example

Here is an example sequence of curl commands to validate the login setup:

  1. Login Request:

Command:

curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "username", "password": "Test1234"}'

Response (example):

{
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0NjZkMTAyNS1iZGNkLTRiNWItYTYxMi0yYThiZTU2MDhlNjIiLCJpYXQiOjE3MTgwNDg1NDQsImV4cCI6MTcxODA1MjE0NH0.Zl2i59w89cgJxfI4lXn6VmOhC5GLEqMm2nWkiVKpEUs",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0NjZkMTAyNS1iZGNkLTRiNWItYTYxMi0yYThiZTU2MDhlNjIiLCJpYXQiOjE3MTgwNDg1NDQsImV4cCI6NDg0MjI1MDk0NH0.xEF7kObwkztrMF7J83S-xvDarABmjXYkqLFINPWbx6g"
}
  1. Invalid Credentials Request:

Command:

curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "testuser", "password": "wrongpassword"}'

Response (example):

{
    "statusCode": 401,
    "message": "Unauthorized"
}

How-To Guides

1. Registering AuthLocalModule Synchronously

// app.module.ts

//...
  AuthLocalModule.register({
    userLookupService: new MyUserLookupService(), // required
  }),
//...

2. Registering AuthLocalModule Asynchronously

// app.module.ts
import { MyUserLookupService } from './services/my-user-lookup.service.ts';

//...
AuthLocalModule.registerAsync({
  useFactory: async (userLookupService: MyUserLookupService) => ({
    userLookupService, // required
  }),
  inject: [MyUserLookupService],
}),
//...

3. Global Registering AuthLocalModule Asynchronously

// app.module.ts

//...
AuthLocalModule.forRootAsync({
  useFactory: async (userLookupService: MyUserLookupService) => ({
    userLookupService, 
  }),
  inject: [MyUserLookupService],
}),
//...

4. Implementing User Lookup Service

// my-user-lookup.service.ts
import { Injectable } from '@nestjs/common';
import {
  AuthLocalUserLookupServiceInterface,
  AuthLocalCredentialsInterface
} from '@concepta/nestjs-auth-local';

@Injectable()
export class MyUserLookupService
  implements AuthLocalUserLookupServiceInterface {
  async byUsername(username: string): Promise<AuthLocalCredentialsInterface | null> {
    // implement custom logic to return the user's credentials
    return null;
  }
}

5. Implementing custom token issuance service

There are two ways to implementing the custom token issue service. You can take advantage of the default service, as seen here:

// my-jwt-issue.service.ts
import { Injectable } from '@nestjs/common';
import {
  JwtIssueService,
  JwtIssueServiceInterface,
  JwtSignService,
} from '@concepta/nestjs-jwt';

@Injectable()
export class MyJwtIssueService extends JwtIssueService {
  constructor(protected readonly jwtSignService: JwtSignService) {
    super(jwtSignService);
  }

  async accessToken(
    ...args: Parameters<JwtIssueServiceInterface['accessToken']>
  ) {
    // your custom code
    return super.accessToken(...args);
  }

  async refreshToken(
    ...args: Parameters<JwtIssueServiceInterface['refreshToken']>
  ) {
    // your custom code
    return super.refreshToken(...args);
  }
}

Or you can completely replace the default implementation:

// my-jwt-issue.service.ts
import { Injectable } from '@nestjs/common';
import { JwtIssueServiceInterface } from '@concepta/nestjs-jwt';

@Injectable()
export class MyJwtIssueService implements JwtIssueServiceInterface {
  constructor() {}

  async accessToken(
    ...args: Parameters<JwtIssueServiceInterface['accessToken']>
  ) {
    // your custom code
  }

  async refreshToken(
    ...args: Parameters<JwtIssueServiceInterface['refreshToken']>
  ) {
    // your custom code
  }
}

6. Implementing a custom user validation service

The same approach can be done for AuthLocalValidateUserService you can either completely override the default implementation or you can take advantage of the default implementation.

// my-auth-local-validate-user.service.ts
import { Injectable } from '@nestjs/common';
import { ReferenceActiveInterface, ReferenceIdInterface } from '@concepta/ts-core';
import {
  AuthLocalValidateUserInterface,
  AuthLocalValidateUserService
} from '@concepta/nestjs-auth-local';

@Injectable()
export class MyAuthLocalValidateUserService
  extends AuthLocalValidateUserService
{
  
  async validateUser(
    dto: AuthLocalValidateUserInterface,
  ): Promise<ReferenceIdInterface> {
    // customize as needed
    return super.validateUser(dto);
  }

  async isActive(
    user: ReferenceIdInterface<string> & ReferenceActiveInterface<boolean>,
  ): Promise<boolean> {
    // customize as needed
    return super.isActive(user);
  }
}
// my-auth-local-validate-user.service.ts
import { Injectable } from '@nestjs/common';
import { ReferenceActiveInterface, ReferenceIdInterface } from '@concepta/ts-core';
import {
  AuthLocalValidateUserInterface,
  AuthLocalValidateUserServiceInterface
} from '@concepta/nestjs-auth-local';

@Injectable()
export class MyAuthLocalValidateUserService
  implements AuthLocalValidateUserServiceInterface
{
  async validateUser(
    dto: AuthLocalValidateUserInterface,
  ): Promise<ReferenceIdInterface> {
    // your custom code
    return {
      id: '[userId]',
      //...
    }
  }

  async isActive(
    user: ReferenceIdInterface<string> & ReferenceActiveInterface<boolean>,
  ): Promise<boolean> {
    // customize as needed
    return true;
  }
}

7. Implementing a custom password validation service

The PasswordValidationService in the @concepta/nestjs-password module provides a default implementation using bcrypt for hashing and verifying passwords. However, depending on your application's requirements, you might need to use a different method for password hashing or add additional validation logic.

You can either extend the existing PasswordValidationService to leverage its built-in functionalities while adding your enhancements, or completely override it with your custom implementation.

Overriding the Default Implementation:

If your application requires a different hashing algorithm , you can replace the default implementation with one that suits your needs.

// my-password-validation.service.ts
import { Injectable } from '@nestjs/common';
import {
  PasswordStorageInterface,
  PasswordValidationServiceInterface,
} from '@concepta/nestjs-password';

@Injectable()
export class MyPasswordValidationService
  implements PasswordValidationServiceInterface
{
  async validate(options: {
    password: string;
    passwordHash: string;
    passwordSalt: string;
  }): Promise<boolean> {
    // customize as needed
    return true;
  }

  async validateObject<T extends PasswordStorageInterface>(
    password: string,
    object: T,
  ): Promise<boolean> {
    // customize as needed
    return true;
  }
}

Extending the Default Service:

If you want to add additional validation logic while keeping the current hashing and validation, you can extend the default service:

// my-password-validation.service.ts
import {
  PasswordStorageInterface,
  PasswordValidationService,
} from '@concepta/nestjs-password';
import { Injectable } from '@nestjs/common';

@Injectable()
export class MyPasswordValidationService extends PasswordValidationService {
  async validate(options: {
    password: string;
    passwordHash: string;
    passwordSalt: string;
  }): Promise<boolean> {
    // customize as neeeded
    return super.validate(options);
  }

  async validateObject<T extends PasswordStorageInterface>(
    password: string,
    object: T,
  ): Promise<boolean> {
    // customize as neeeded
    return super.validateObject(password, object);
  }
}

8. Overriding the Settings

import { Type } from '@nestjs/common';

export class CustomLoginDto {
  email: string;
  password: string;
}

export const localSettings = {
  loginDto: CustomLoginDto,
  usernameField: 'email',
  passwordField: 'password'
};
AuthLocalModule.forRoot({
  userLookupService: new UserLookupService(),
  issueTokenService: new MyIssueTokenService(), // <- optional
  passwordValidationService: new PasswordValidationService(), // <- optional
  settings: localSettings
}),

9. Integration with Other NestJS Modules

Integrate nestjs-auth-local with other NestJS modules like, @concepta/nestjs-authentication, @concepta/nestjs-auth-jwt, @concepta/nestjs-auth-refresh for a comprehensive authentication system.

Reference

For detailed information on the properties, methods, and classes used in the @concepta/nestjs-auth-local, please refer to the API documentation available at AuthLocalModule API Documentation. This documentation provides comprehensive details on the interfaces and services that you can utilize to customize and extend the authentication functionality within your NestJS application.

Explanation

Conceptual Overview of Local Authentication

What is Local Authentication?

Local Authentication is a method of verifying user identity based on credentials (username and password) stored locally within the application or in a connected database.

Benefits of Using Local Authentication

  • Simplicity: Easy to implement and manage.
  • Control: Full control over user authentication and data.
  • Security: When properly implemented, provides a secure way to authenticate users.

Design Choices in AuthLocalModule

Why Use Local Authentication?

Local Authentication is ideal for applications that need to manage user authentication directly within the application without relying on external identity providers.

Synchronous vs Asynchronous Registration

  • Synchronous Registration: Used when configuration options are static and available at startup.

  • Asynchronous Registration: Used when configuration options need to be retrieved from external sources at runtime.

Global vs Feature-Specific Registration

  • Global Registration: Makes the module available throughout the entire application.

  • Feature-Specific Registration: Allows the module to be registered only for specific features or modules within the application.

Readme

Keywords

none

Package Sidebar

Install

npm i @concepta/nestjs-auth-local

Weekly Downloads

88

Version

4.0.0

License

BSD-3-Clause

Unpacked Size

60.3 kB

Total Files

39

Last publish

Collaborators

  • mrmaz
  • leoafarias