Skip to content

AuthNModule

The AuthNModule (authentification module) provides functionality for user authentication in a NestJS application. It includes methods to sign up new users, sign in existing users, and invite new users. It also supports the use of JSON Web Tokens (JWT) for authentication and one-time passwords (OTP) for added security. The module can be used to manage user access to different parts of the application based on their authentication status.

Entity Inheritance

NestKit provide base classes that can be inherited and customized with your needs. For entities we use TypeORM TableInheritance.

Key Components

The module provides a set of reusable components that can be easily integrated into a NestJS project:

RefreshToken Entity

Refresh tokens are long-lived tokens used in authentication systems to obtain new access tokens. Access tokens have a limited lifespan and need to be periodically renewed to maintain user authentication. When an access token expires, the user can use a refresh token to request a new access token without having to re-enter their login credentials.

The RefreshToken database record is used for storing the necessary information about active refresh tokens. It includes the token ID, expiration date, creation date, audience (scopes), and user ID that the token belongs to. This record allows for the revocation of refresh tokens, ensuring the security of the user's account.

JwtService

JwtService is a service that provides functionality for generating, verifying, and refreshing JSON Web Tokens (JWTs) in a NestJS application.

It can be used to generate access and refresh tokens for users and then verify those tokens when they are presented to the application. In addition, the JwtService can refresh an expired access token with a valid refresh token, and it can delete a refresh token from the database when it is no longer needed.

The service uses a secret key to sign and verify the tokens, and it can be configured to expire the tokens after a certain period of time. With JwtService, NestJS applications can implement secure authentication and authorization for users.

OtpService

OtpService (One-Time Password Service) is a service that provides the functionality to configure OTP, generate secret tokens for the authenticator, and verify and disable OTP for users. It is commonly used for adding an additional layer of security to the authentication process.

With OtpService, users can enable OTP to receive a one-time code to verify their identity when they log in. The service can generate a unique secret token for each user, which is then used by the authenticator app to generate one-time codes.

OtpService can verify the code entered by the user and grant access upon successful verification. Additionally, users can disable OTP for their account if they no longer wish to use it.

AuthNService

AuthNService is a core component of the AuthNModule, responsible for handling user authentication processes in a NestJS application. It offers a range of methods for user registration, login, and invitation, while leveraging JSON Web Tokens (JWT) and one-time passwords (OTP) for enhanced security. AuthNService effectively manages user access control, ensuring seamless navigation through various application features based on their authentication status.

AuthNResolver

NestJS AuthNResolver is a crucial part of the AuthNModule that bridges the gap between GraphQL API and the AuthNService. It exposes authentication-related functionalities such as user registration, login, and invitation through GraphQL mutations and queries. By integrating with the AuthNService, AuthNResolver ensures secure and efficient management of user authentication within a NestJS application utilizing GraphQL.

AuthNController

NestJS AuthNController is a vital component of the AuthNModule that handles token refreshing in a NestJS application using HTTP requests. It enables clients to obtain a new access token seamlessly, ensuring uninterrupted access to protected resources.

How to install AuthNModule to my app

To create a AuthNModule in NestJS, you can follow these steps:

  1. Install the required dependencies for AuthNModule:

  2. Create a auth-n folder in your project's src/modules directory.

  3. Create a jwt folder in src/modules/auth-n directory.

  4. Inside the jwt folder, create a jwt.service.ts file and extends BaseJwtService from NestKit with the following code:

    ts
    // src/modules/auth-n/jwt/jwt.service.ts
    
    import { User } from '@/modules/users/user.entity';
    import { BaseJwtService } from '@deeepvision/nest-kit/dist/modules/auth-n';
    import { Injectable } from '@nestjs/common';
    import { RefreshToken } from '../entities/refresh-token.entity';
    
    @Injectable()
    export class JwtService extends BaseJwtService<User, RefreshToken> {}
  5. Create a otp folder in src/modules/auth-n directory.

  6. Inside the otp folder, create a otp.service.ts file and extends BaseOtpService from NestKit with the following code:

    ts
    // src/modules/auth-n/otp/otp.service.ts
    
    import { User } from '@/modules/users/user.entity';
    import { BaseOtpService } from '@deeepvision/nest-kit/dist/modules/auth-n';
    import { Injectable } from '@nestjs/common';
    
    @Injectable()
    export class OtpService extends BaseOtpService<User> {}
  7. Inside the otp folder, create a otp.resolver.ts file and extends OtpResolver with the following code:

    ts
    // src/modules/autn-n/otp/otp.resolver.ts
    
    import { User } from '@/modules/users/user.entity';
    import { BaseOtpResolver } from '@deeepvision/nest-kit/dist/modules/auth-n';
    import { Resolver } from '@nestjs/graphql';
    
    @Resolver()
    export class OtpResolver extends BaseOtpResolver(User) {}
  8. Create an entities folder in src/modules/auth-n directory.

  9. Inside the auth-n/entities folder, create a refresh-token.entity.ts file and extends BaseRefreshToken from NestKit with the following code:

    ts
    // src/modules/auth-n/entities/refresh-token.entity.ts
    
    import { ChildEntity } from 'typeorm';
    import { BaseRefreshToken } from '@deeepvision/nest-kit/dist/modules/auth-n';
    import { User } from '@/modules/users/user.entity';
    
    @ChildEntity()
    export class RefreshToken extends BaseRefreshToken {
      user!: Promise<User>;
    }
  10. Inside the auth-n folder, create a auth-n.controller.ts file with the following code:

    ts
    // src/modules/autn-n/auth-n.controller.ts
    
    import { BaseAuthNController } from '@deeepvision/nest-kit/dist/modules/auth-n';
    
    import { UserToRole } from '../user-to-roles/user-to-role.entity';
    import { User } from '../users/user.entity';
    import { RefreshToken } from './entities/refresh-token.entity';
    
    export class AuthNController extends BaseAuthNController<User, UserToRole, RefreshToken>() {}
  11. Inside the auth-n folder, create a auth-n.service.ts file and extends BaseAuthNService with the following code:

    ts
    // src/modules/autn-n/auth-n.service.ts
    
    import { BaseAuthNService } from '@deeepvision/nest-kit/dist/modules/auth-n';
    import { UserToRole } from '../user-to-roles/user-to-role.entity';
    import { User } from '../users/user.entity';
    import { RefreshToken } from './entities/refresh-token.entity';
    
    export class AuthNService extends BaseAuthNService<User, UserToRole, RefreshToken> {}
  12. Inside the auth-n folder, create a auth-n.resolver.ts file and extends BaseAuthNResolver with the following code:

    ts
    // src/modules/autn-n/auth-n.resolver.ts
    
    import { BaseAuthNResolver } from '@deeepvision/nest-kit/dist/modules/auth-n';
    import { Resolver } from '@nestjs/graphql';
    import { User } from '@/modules//users/user.entity';
    import { RefreshToken } from './entities/refresh-token.entity';
    
    @Resolver()
    export class AuthNResolver extends BaseAuthNResolver(User, RefreshToken) {}
  13. Inside the auth-n folder, create a auth-n.module.ts file with the following code:

    ts
    // src/modules/auth-n/auth-n.module.ts
    
    import { Module } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { UserToRole } from '../user-to-roles/user-to-role.entity';
    import { OrganizationsModule } from '../organizations/organizations.module';
    import { User } from '../users/user.entity';
    import { UsersModule } from '../users/users.module';
    import { AuthNController } from './auth-n.controller';
    import { AuthNResolver } from './auth-n.resolver';
    import { AuthNService } from './auth-n.service';
    import { JwtService } from './jwt/jwt.service';
    import { OtpResolver } from './otp/otp.resolver';
    import { OtpService } from './otp/otp.service';
    import { RefreshToken } from './entities/refresh-token.entity';
    import {
      AUTHN_SERVICE_TOKEN, JWT_SERVICE_TOKEN, OTP_SERVICE_TOKEN,
    } from '@deeepvision/nest-kit/dist/modules/auth-n';
    
    @Module({
      imports: [
        TypeOrmModule.forFeature([RefreshToken, User, UserToRole]),
        OrganizationsModule,
        UsersModule,
      ],
      controllers: [AuthNController],
      providers: [
        AuthNService,
        {
          provide: AUTHN_SERVICE_TOKEN,
          useExisting: AuthNService,
        },
        AuthNResolver,
        JwtService,
        {
          provide: JWT_SERVICE_TOKEN,
          useExisting: JwtService,
        },
        OtpService,
        {
          provide: OTP_SERVICE_TOKEN,
          useExisting: OtpService,
        },
        OtpResolver,
      ],
    })
    export class AuthNModule {}
  14. Finally, import the AuthNModule into your main AppModule or another module:

    ts
    // src/modules/app.module.ts
    
    import { Module } from '@nestjs/common';
    import { AuthNModule } from './modules/auth-n/auth-n.module.ts';
    
    @Module({
      imports: [AuthNModule],
    })
    export class AppModule {}

    In this code, we are importing the AuthNModule and adding it to the imports array of our AppModule.

How to sign up new user

The signUp method in AuthNService becomes incredibly powerful when integrated with a GraphQL mutation. With, AuthNResolver you can easily expose the signUp functionality to clients, allowing them to register new users with a single, flexible API request.

The assignDefaultRolesAfterSignUp method allows developers to automatically assign default roles to newly signed up users within an organization. By overloading this method and providing the necessary AssignDefaultRolesAfterSignUpOptions, which includes the user's ID and the organization ID they belong to, the system can seamlessly set up default permissions for new users, streamlining the onboarding process and ensuring a consistent user experience.

ts

@Injectable()
export class AuthNService extends BaseAuthNService<User, UserToRole, RefreshToken> {
  constructor(/* Add required dependencies */) {
    super(/* Pass required dependencies to the parent constructor */);
  }

  // Overloading the assignDefaultRolesAfterSignUp method
  async assignDefaultRolesAfterSignUp(opts: AssignDefaultRolesAfterSignUpOptions): Promise<void> {
    const { userId, organizationId } = opts;

    // Logic for assigning default roles to the new user
    await this.usersService.update({
      id: opts.userId,
      userToRoles: [{
        organizationId: opts.organizationId,
        roleId: SystemRoles.USER,
      }],
    }, ctx);
  }
}

Sign up user with GraphQL

Here's an example GraphQL mutation to sign up for the app

How to register user with approval

Implementing a registration process that requires administrator approval can be crucial for applications needing an additional layer of user validation. This section will guide you through setting up such a process using the AuthConfig and handling user approvals or rejections.

Step 1: Configure AuthConfig for Approval

First, you must ensure that the AuthConfig is installed and correctly configured to require administrator approval for new user registrations. To achieve this, set the waitingForApprovalAfterRegistration option to true. This setting ensures that all newly registered users will have their status set to WAITING_FOR_APPROVAL, indicating they are pending administrator approval.

Here's how to set up the AuthConfig:

ts
import { createAuthConfig } from '@deeepvision/nest-kit/dist/configs';

// Configure AuthConfig with waitingForApprovalAfterRegistration set to true
export const AuthConfig = createAuthConfig({
  waitingForApprovalAfterRegistration: true,
});

This configuration step is crucial, as it lays the foundation for the approval-based registration process.

Step 2: Approve or Reject Users

Once the AuthConfig is set up to require approval, administrators can manage user approvals through the provided mechanisms. This can be done either through GraphQL mutations if your application uses GraphQL or directly in your code by utilizing services from the UsersModule.

Approving or Rejecting via GraphQL Mutations

If your application is set up with GraphQL, administrators can use the approveUser or rejectUser mutations provided by the UsersModule. These mutations allow for straightforward management of user approvals directly through GraphQL interfaces.

Here is an example mutation for approving a user:

graphql
mutation ApproveUser($input: ApproveUserInput!) {
  approveUser(input: $input) {
    id
    status
  }
}

And for rejecting a user:

graphql
mutation RejectUser($input: RejectUserInput!) {
  rejectUser(input: $input) {
    id
    status
  }
}
json
{
  "input": {
    "id": "user_id_here"
  }
}

Replace "user_id_here" with the actual ID of the user you wish to approve or reject.

Approving or Rejecting Users Programmatically

For applications that prefer or require approving users programmatically, you can leverage the UsersService provided by the UsersModule. Specifically, you can use the UsersService.approve and UsersService.reject methods to approve or reject users within your application's code.

Here's how you might use these methods in a service:

ts
@Injectable()
export class SomeService {
  constructor(private usersService: UsersService) {}

  async approveUser(userId: string) {
    return this.usersService.approve(userId);
  }

  async rejectUser(userId: string) {
    return this.usersService.reject(userId);
  }
}

How to invite user to organization

The "Invite User" feature enables administrators to invite new users to join an organization and assign them a specific role within the system. By utilizing the inviteUser mutation, you can streamline the onboarding process and maintain control over user access and permissions. This feature ensures that new users receive an invitation to join the platform and are automatically associated with the appropriate role, enabling them to access relevant resources and functionality within the organization.

Invite user with GraphQL

Here's an example GraphQL mutation to invite user to organization.

Below is a list of user invitation statuses, representing the various stages an invited user may go through in the system:

  1. PENDING: The initial status assigned to a user upon creation during the invitation process.
  2. EMAIL_SENT: The status changes to EMAIL_SENT when the system successfully sends an invitation email to the user.
  3. EMAIL_OPENED: The status updates to EMAIL_OPENED when the user opens the invitation email.
  4. LINK_OPENED: The status becomes LINK_OPENED when the user clicks on the invitation link in the email.
  5. LOGGED_IN: The LOGGED_IN status indicates that the user has successfully logged in for the first time after receiving an invitation. At this stage, the user is presented with the "Account Setup" form, where they are required to provide profile information and set up a new password to complete the onboarding process.
  6. DONE: The status updates to DONE when the user has set up their basic information and new password.
  7. ERROR: The status changes to ERROR when something goes wrong during the invitation process.

The sendInvitation mutation allows administrators to resend invitation emails to users who may not have received their initial invitation.

Resend invitation with GraphQL

Here's an example GraphQL mutation to resend invitation.

How to sign in within an application

The signIn mutation allows users to securely authenticate themselves within the application using their email and password. Clients can send an API request to validate user credentials and obtain access to protected resources.

If you have successfully signed in, you can obtain your profile information using AuthContext or me query.

Sign In with GraphQL

Here's an example GraphQL mutation to sign in within an application.

How to sign out from a system

The signOut mutation allows users to securely log out of their accounts by revoking their refresh token. This action prevents the access token from being refreshed on other devices, thereby enhancing security and ensuring that unauthorized users cannot continue using the logged-out user's account. By invoking the signOut mutation, users can maintain better control over their active sessions and protect their account information.

Sign Out with GraphQL

Here's an example GraphQL mutation to sign out from the system

How to sign in as another user

The "Sign In as Another User" feature provides superadmins and admins with the ability to seamlessly impersonate other users within the system. Privileged users can temporarily assume the identity of another user. This functionality enables administrators to access and troubleshoot user-specific issues, while ensuring a smooth user experience.

Here's an example GraphQL mutation to sign in as another user.

When a superadmin logs in as another user, they obtain access and refresh tokens with a pid property that contains the superadmin's ID who signed in as this user. For each request, the user in the request context has a parentUserId that contains the superadmin's ID who signed in as this user.

Here's an example GraphQL query me with the parentUserId field.

You can use this property to show a "Sign out and return to superadmin account" button.

When the user clicks on "Sign out" you have to call the signOutAsUser mutation and provide the current refresh token. After signing out, the server returns the superadmin's access and refresh tokens, and you can sign in again to the superadmin profile.

Here's an example GraphQL mutation to sign out as a user.

How to configure One-Time Password

The configureOtp method is used to set up and configure One-Time Password (OTP) functionality for a specific user. It takes a ConfigureOtpInput object as input, which contains the userId for whom the OTP is to be configured. The method returns an ConfigureOtpPayload object that includes the secret key for generating OTP codes, the otpAuthUrl for configuring the OTP in the user's OTP app (can be used for QRCode), and the user object containing details of the user being configured.

Here's an example GraphQL mutation to configure One-Time Password for user.

How to confirm the One-Time Password after linking the Authenticator App

The verifyOtp mutation is used to validate the One-Time Password (OTP) token provided by the user for the first time and enable OTP functionality for their account.

It takes a VerifyOtpInput object as input, which contains the userId of the user being verified and the token representing the OTP code. If the token is valid, the mutation enables OTP for the user and returns an VerifyOtpPayload object containing the user's details.

Here's an example GraphQL mutation to verify token at the first time and enable One-Time Password for user.

How to authentificate user with One-Time Password

When a user with enabled two-factor authentication (2FA) and One-Time Password (OTP) calls the signIn mutation, the system does not return JWT access and refresh tokens immediately. Instead, the user is required to go through an additional authentication step by being redirected to the 2FA form, where they must input the OTP code generated by their authenticator app to successfully sign in.

ts
if (user.is2faEnabled && user.isOtpEnabled) {
  // go to 2fa form
}

Here's an example GraphQL mutation to sign in with enabled 2fa.

The validateOtp mutation is used to check the One-Time Password (OTP) entered by a user who has enabled two-factor authentication (2FA). After being redirected to the 2FA form, the user inputs the OTP code generated by their authenticator app. The validateOtp mutation checks the validity of the provided code and, if successful, returns the authenticated user information along with a new JWT access and refresh token.

Here's an example GraphQL mutation to validate OTP code.

How to disable the One-Time Password for user

The disableOtp mutation allows a user to disable One-Time Password (OTP) two-factor authentication for their account. To use this mutation, the user must provide their userId as an input. Once the mutation is successfully executed, the OTP feature will be deactivated, and the user will no longer need to provide OTP codes during the authentication process.

Here's an example GraphQL mutation to disable OTP for user.

Created by DeepVision Software.