Authentication with Clerk in NestJS Server Application
January 5, 2025

Authentication with Clerk in NestJS Server Application


introduce

This article provides a comprehensive step-by-step guide to implementing authentication and authorization in a NestJS backend application using Clerk.


What is a clerk?

Clerk is a comprehensive platform that provides an embeddable user interface, flexible APIs, and an intuitive and powerful dashboard for seamless user authentication and management. It covers everything from session management and multi-factor authentication to social login, magic links, email or SMS one-time passwords and more.


Why use clerks?

As data protection and privacy become increasingly important, authentication and security requirements, trends and best practices are always evolving. By offloading these responsibilities to a professional services provider, you can focus on building the core functionality of your application and deliver it faster.

Platforms like Clerk can take on these security tasks for you.


Prerequisites

  • Basic knowledge of Typescript
  • Familiar with NestJS basics
  • Understand backend authentication concepts
  • Running Node 18 or latest version


Project settings

This project requires a new or existing NestJS project, a Clerk account and application, and the Passport, Passport Strategy, and Clerk backend SDK libraries.


Create a NestJS project

You can use the Nest CLI to easily set up new NestJS projects. Using any package manager you prefer, execute the following command to create a new Nest app:

$ pnpm add -g @nestjs/cli
$ nest new clerk-auth
Enter full screen mode

Exit full screen mode

Checkout NestJS documentation Learn more.


Set up your Clerk account and application

If you don’t have a Clerk account yet, create a Clerk account and set up a new application in the Clerk dashboard. You can start Clerk Website.


Install required libraries

The libraries required for this project can be installed using the following command:

$ pnpm add @clerk/backend @nestjs/config @nestjs/passport passport passport-custom
Enter full screen mode

Exit full screen mode


Environment configuration

Create a .env The file is located in the root directory of the project and is used to manage variables in different environments. production, development or staging.

Add the following variables, replacing the placeholders with the actual keys obtained from the Clerk Account Dashboard.

# .env

CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
CLERK_SECRET_KEY=YOUR_SECRET_KEY
Enter full screen mode

Exit full screen mode

Use the following commands to access environment variables throughout your application ConfigServiceimport ConfigModule into the roots AppModule.

// src/app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
  ],
})
export class AppModule {}
Enter full screen mode

Exit full screen mode



Integrating Clerk in NestJS

This section describes how to integrate and use the Clerk backend SDK in a NestJS project.


Create Clerk client provider

Registering the Clerk client as a provider allows it to be injected into a class using a decorator so that it can be used wherever needed throughout the code base, as shown in the next section.

// src/providers/clerk-client.provider.ts

import { createClerkClient } from '@clerk/backend';
import { ConfigService } from '@nestjs/config';

export const ClerkClientProvider = {
  provide: 'ClerkClient',
  useFactory: (configService: ConfigService) => {
    return createClerkClient({
      publishableKey: configService.get('CLERK_PUBLISHABLE_KEY'),
      secretKey: configService.get('CLERK_SECRET_KEY'),
    });
  },
  inject: [ConfigService],
};
Enter full screen mode

Exit full screen mode


Register ClerkClientProvider in AppModule

Next, you need to register the provider with Nest to enable dependency injection.

// src/app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ClerkClientProvider } from 'src/providers/clerk-client.provider';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
  ],
  providers: [ClerkClientProvider],
})
export class AppModule {}
Enter full screen mode

Exit full screen mode



Use a passport with a JWT issued by a clerk

Clerk issues a JWT token when a user registers or logs in through Clerk’s hosted page or front-end application. Then use that token as in bearer token Authorization header The number of requests made to the NestJS backend application.


Develop clerical strategy

In NestJS, Passport is the recommended way to implement authentication policies. You will create a custom Clerk policy for using Clerk client authentication tokens.

// src/auth/clerk.strategy.ts

import { User, verifyToken } from '@clerk/backend';
import { Injectable, Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-custom';
import { UsersService } from 'src/users/users.service';
import { Request } from 'express';
import { ClerkClient } from '@clerk/backend';

@Injectable()
export class ClerkStrategy extends PassportStrategy(Strategy, 'clerk') {
  constructor(
    @Inject('ClerkClient')
    private readonly clerkClient: ClerkClient,
    private readonly configService: ConfigService,
  ) {
    super();
  }

  async validate(req: Request): Promise<User> {
    const token = req.headers.authorization?.split(' ').pop();

    if (!token) {
      throw new UnauthorizedException('No token provided');
    }

    try {
      const tokenPayload = await verifyToken(token, {
        secretKey: this.configService.get('CLERK_SECRET_KEY'),
      });

      const user = await this.clerkClient.users.getUser(tokenPayload.sub);

      return user;
    } catch (error) {
      console.error(error);
      throw new UnauthorizedException('Invalid token');
    }
  }
}

Enter full screen mode

Exit full screen mode

this validate() The method returns the user data that NestJS automatically appends to request.user.


Create an authentication module

Create a AuthModule Provide Clerk strategies and work with PassportModule. Then, register AuthModule exist AppModule.

// src/auth/auth.module.ts

import { Module } from '@nestjs/common';
import { ClerkStrategy } from './clerk.strategy';
import { PassportModule } from '@nestjs/passport';
import { ClerkClientProvider } from 'src/providers/clerk-client.provider';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [PassportModule, ConfigModule],
  providers: [ClerkStrategy, ClerkClientProvider],
  exports: [PassportModule],
})
export class AuthModule {}

Enter full screen mode

Exit full screen mode

// src/app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ClerkClientProvider } from 'src/providers/clerk-client.provider';
import { AuthModule } from 'src/auth/auth.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    AuthModule,
  ],
  providers: [ClerkClientProvider],
})
export class AppModule {}
Enter full screen mode

Exit full screen mode



Implement route protection

Protected routes are routes that require authenticated users to access.


Create Clerk Authentication Guard

Guards decide whether a route handler should handle a specific request based on certain runtime conditions.

If you want to protect all routes in your application by default, you need to follow these steps:

  1. Create a Public Decorator to mark routes accessible without authentication.
  2. implement a ClerkAuthGuard Restrict access to protected routes, allowing only authenticated users to proceed.
// src/decorators/public.decorator.ts

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

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

Enter full screen mode

Exit full screen mode

// src/auth/clerk-auth.guard.ts

import { type ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { IS_PUBLIC_KEY } from 'src/decorators/public.decorator';

@Injectable()
export class ClerkAuthGuard extends AuthGuard('clerk') {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) {
      return true;
    }

    return super.canActivate(context);
  }
}
Enter full screen mode

Exit full screen mode


Enable authentication domain-wide

Since most endpoints are protected by default, you can configure authentication protection as domain-wide protection.

// src/app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ClerkClientProvider } from '../providers/clerk-client.provider';
import { AuthModule } from '../auth/auth.module';
import { ClerkAuthGuard } from '../auth/clerk-auth.guard';
import { APP_GUARD } from '@nestjs/core';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    AuthModule,
  ],
  providers: [
    ClerkClientProvider,
    {
      provide: APP_GUARD,
      useClass: ClerkAuthGuard,
    },
  ],
})
export class AppModule {}
Enter full screen mode

Exit full screen mode


Define protected public routes

In these two controllers, Public Decorators are used for AppController Designate the route as public. In contrast, no decorators are required AuthController Specify the route as protected because authentication protection is applied domain-wide by default.

// src/app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { Public } from 'src/decorators/public.decorator';

@Controller()
export class AppController {
  @Public()
  @Get()
  async app() {
    return 'Hello World';
  }
}
Enter full screen mode

Exit full screen mode

// src/auth/auth.controller.ts

import { User } from '@clerk/backend';
import { Controller, Get } from '@nestjs/common';
import { CurrentUser } from 'src/decorators/current-user.decorator';

@Controller('auth')
export class AuthController {
  @Get('me')
  async getProfile(@CurrentUser() user: User) {
    return user;
  }
}
Enter full screen mode

Exit full screen mode

notes: Remember to register AppController exist AppModule and AuthController exist AuthModule.


in conclusion

Clerk serves as a platform that handles identity verification and security responsibilities, keeping up with the latest trends and best practices. This allows you to focus on building the core functionality of your application and accelerates your development process.

In this guide, we cover the steps to implement Clerk authentication, from setting up your project to securing your routes. These basic steps should help you start exploring the possibilities of an identity verification service platform.

A fully functional example of this project is included at the end of this article.

Using Clerk authentication and user management in NestJS backend applications

Using Clerk authentication and user management in NestJS backend applications

What’s inside?

The monorepo contains the following packages and applications:

Applications and packages

Every package and app is 100% typescript.

Utilities

This monorepo has some additional tools set up for you:

2025-01-04 22:45:15

Leave a Reply

Your email address will not be published. Required fields are marked *