Appearance
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:
Install the required dependencies for
AuthNModule
:Create a
auth-n
folder in your project'ssrc/modules
directory.Create a
jwt
folder insrc/modules/auth-n
directory.Inside the
jwt
folder, create ajwt.service.ts
file and extendsBaseJwtService
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> {}
Create a
otp
folder insrc/modules/auth-n
directory.Inside the
otp
folder, create aotp.service.ts
file and extendsBaseOtpService
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> {}
Inside the
otp
folder, create aotp.resolver.ts
file and extendsOtpResolver
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) {}
Create an
entities
folder insrc/modules/auth-n
directory.Inside the
auth-n/entities
folder, create arefresh-token.entity.ts
file and extendsBaseRefreshToken
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>; }
Inside the
auth-n
folder, create aauth-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>() {}
Inside the
auth-n
folder, create aauth-n.service.ts
file and extendsBaseAuthNService
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> {}
Inside the
auth-n
folder, create aauth-n.resolver.ts
file and extendsBaseAuthNResolver
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) {}
Inside the
auth-n
folder, create aauth-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 {}
Finally, import the
AuthNModule
into your mainAppModule
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 theimports
array of ourAppModule
.
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:
- PENDING: The initial status assigned to a user upon creation during the invitation process.
- EMAIL_SENT: The status changes to
EMAIL_SENT
when the system successfully sends an invitation email to the user. - EMAIL_OPENED: The status updates to
EMAIL_OPENED
when the user opens the invitation email. - LINK_OPENED: The status becomes
LINK_OPENED
when the user clicks on the invitation link in the email. - 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. - DONE: The status updates to
DONE
when the user has set up their basic information and new password. - 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.
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.