Clerk JWT Authentication with NestJs + Passport for REST & GraphQL
Tags: nestjs, clerk, graphql, restapi
Published: 2023-10-18 23:20:30

If you're like me. You already love using [Clerk](https://clerk.com/) to handle all Authentication for you in the Frontend.

However, I recently needed to connect my NextJs+Clerk frontend with a NestJs backend. The docs do a good job explaining how to implement a [Manual JWT Verification](https://clerk.com/docs/backend-requests/handling/manual-jwt) for the backend endpoints. But I couldn't find specific info on how to implement it with NestJs.

Here is the solution I made using PassportJs

---

## Get your Clerk Secret Keys

To connect our NestJs application with Clerk, we'll need the and the **JWT public key** from the [Api Keys section](https://clerk.com/docs/backend-requests/handling/manual-jwt#:~:text=obtained%20from%20the-,API%20Keys,-section%20of%20the)

![Clerk Dashboard JWT public key location](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0g3we5yu1txqenpwyeat.png)

Copy and save the JWT public key **without the "/.well-known/jwks.json"** part.

We will need it to validate the JWT coming from the frontend.

---

## NestJs basic setup


Let's create a new Nestjs project.

```bash
$ nest new clerk-w-nest
```

I'm gonna go ahead and choose npm but u can choose any you like.

### REST endpoint

Now let's create some rest endpoints.

```bash
$ nest g resource rest-endpoints
```

![nest generate REST API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t2ly9br5d2qofsckk3fq.png)

Let's go ahead and create a new endpoint called pingOne under our Controller Class.

```ts
// src/rest-endpoints/rest-endpoints.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { RestEndpointsService } from './rest-endpoints.service';

@Controller('rest-endpoints')
export class RestEndpointsController {
  constructor(private readonly restEndpointsService: RestEndpointsService) {}

  @Get(':id')
  pingOne(@Param('id') id: string) {
    return this.restEndpointsService.pingOne(id);
  }
}
```

And modify our Service Class.

```ts
// src/rest-endpoints/rest-endpoints.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class RestEndpointsService {
  pingOne(id: string) {
    return `REST Ping #${id}!`;
  }
}
```

Now lets test the endpoint

Start the server `npm run start:dev`

And test it

```
$ curl http://localhost:3000/rest-endpoints/12345
REST Ping #12345!
```

Good! Now let's do the GraphQL Endpoint

### GraphQL Endpoint

```bash
$ nest g resource graphql-endpoints
```


![GraphQL NestJs step](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ltg7yz79k348dsprmeja.png)


You'll also have to install these to use NestJs with GraphQL per the [official documentation](https://docs.nestjs.com/graphql/quick-start).

```bash
npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql
```

And add this line to your `./app.module.ts`

```ts
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ApolloDriver } from '@nestjs/apollo';
import { RestEndpointsModule } from './rest-endpoints/rest-endpoints.module';
import { GraphqlEndpointsModule } from './graphql-endpoints/graphql-endpoints.module';
import { GraphQLModule } from '@nestjs/graphql';

@Module({
  imports: [
    GraphQLModule.forRoot({ autoSchemaFile: true, driver: ApolloDriver }),
    RestEndpointsModule,
    GraphqlEndpointsModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
```

And now let's repeat the process from rest-endpoints


For your Resolver Class

```ts
// src/graphql-endpoints/graphql-endpoints.resolver.ts
import { Resolver, Query, Args } from '@nestjs/graphql';
import { GraphqlEndpointsService } from './graphql-endpoints.service';
import { GraphqlEndpoint } from './entities/graphql-endpoint.entity';

@Resolver(() => GraphqlEndpoint)
export class GraphqlEndpointsResolver {
  constructor(
    private readonly graphqlEndpointsService: GraphqlEndpointsService,
  ) {}

  @Query(() => String, { name: 'pingOne' })
  pingOne(@Args('id') id: string) {
    return this.graphqlEndpointsService.pingOne(id);
  }
}
```

For your Service Class

```ts
// src/graphql-endpoints/graphql-endpoints.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class GraphqlEndpointsService {
  pingOne(id: number) {
    return `GraphQL Ping #${id}!`;
  }
}

```

Now lets go to `http://localhost:3000/graphql` and test it


![Testing GraphQL query](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7lp68qu1c07tdck8nlmi.png)


Good! Now to the interesting part

---

## Creating Passport Auth Module

Let's start by creating a Nest Module for the Authentication

```bash
nest generate module authentication
```


### Setting up Passport

Passport helps us setup our desired Authentication Strategy. We'll use the JSON Web Token (JWT) Passport Strategy to connect it with Clerk.

```bash
$ npm i passport @nestjs/passport passport-jwt jwks-rsa
```

The @nestjs/passport module helps us wrap this Strategy for NestJs.


To implement the Passport Strategy, first add the JWT issuer Url we copied from our Clerk Dashboard [Api Keys section](https://clerk.com/docs/backend-requests/handling/manual-jwt#:~:text=obtained%20from%20the-,API%20Keys,-section%20of%20the) to our `.env` file.

Next you'll need to install `@nestjs/config` to access the env variable.

```
$ npm install @nestjs/config
```

Lets update our App module to use the `.env` file.

```ts
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ApolloDriver } from '@nestjs/apollo';
import { RestEndpointsModule } from './rest-endpoints/rest-endpoints.module';
import { GraphqlEndpointsModule } from './graphql-endpoints/graphql-endpoints.module';
import { GraphQLModule } from '@nestjs/graphql';
import { AuthenticationModule } from './authentication/authentication.module';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: [`.env`],
      isGlobal: true,
    }),
    GraphQLModule.forRoot({ autoSchemaFile: true, driver: ApolloDriver }),
    RestEndpointsModule,
    GraphqlEndpointsModule,
    AuthenticationModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

```

And now we can create our Passport Strategy

```ts
// src/authentication/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';
import * as dotenv from 'dotenv';
import { ConfigService } from '@nestjs/config';

dotenv.config();

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private configService: ConfigService) {
    super({
      secretOrKeyProvider: passportJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: `${configService.get(
          'CLERK_ISSUER_URL',
        )}/.well-known/jwks.json`,
      }),

      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      issuer: `${configService.get('CLERK_ISSUER_URL')}`,
      algorithms: ['RS256'],
    });
    console.log('JwtStrategy initialized');
  }

  validate(payload: unknown): unknown {
    // This one is really useful to check the jwt payload!
    // console.log('Validating payload:', payload);
    return payload;
  }
}

```

In NestJS, to make a Passport Strategy, you simply extend the class you get from the PassportStrategy function, which comes from @nestjs/passport. You tell it which strategy you're using by giving it the JWT Strategy from passport-jwt.

You pass the config for the strategy in the constructor via super(). This setup ensures we can decode JWT tokens and sets the API to recognize tokens with the RS256 sign.

RS256 is chosen because it's a widely-accepted RSA signature-based algorithm that provides strong security by using a private key for signing and a public key for verification, ensuring the JWT wasn't altered after its creation.

NestJS replaces the usual verify callback with a validate() method. But here's the cool part: Clerk does the hard part of authenticating the user. By the time `validate()` is called, Clerk already knows who the user is and gives you their details in the payload. Your app then attaches this to the request, so you can use it anywhere, like in controllers or middleware.


The next step is to create our custom Authentication Guard that extends from Passport's AuthGuard.

```ts
// src/authentication/jwt-auth.guard.ts
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req;
  }
}
```

The JwtAuthGuard is a custom guard tailored for both REST and GraphQL in a NestJS application. It extends the default AuthGuard and specifies the 'jwt' strategy. The `getRequest` method part helps integrate the guard with GraphQL. When invoked, it takes the current ExecutionContext, converts it to a GraphQL-specific context using GqlExecutionContext.create, and then extracts and returns the request (req) object. This ensures that our JWT authentication works seamlessly across both RESTful routes and GraphQL queries or mutations. 


Lastly lets update our Authentication Module 


```ts
// src/authentication/authentication.module.ts
import { Module } from '@nestjs/common';
import { JwtStrategy } from './jwt.strategy';
import { JwtAuthGuard } from './jwt-auth.guard';

@Module({
  providers: [JwtStrategy, JwtAuthGuard],
})
export class AuthenticationModule {}

```


Good! We are done. Now lets add our custom guards to our endpoints to test them out.


In our `src/rest-endpoints/rest-endpoints.controller.ts` we add the guard to our ping endpoint. 

```ts
// src/rest-endpoints/rest-endpoints.controller.ts
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { RestEndpointsService } from './rest-endpoints.service';
import { JwtAuthGuard } from 'src/authentication/jwt-auth.guard';

@Controller('rest-endpoints')
export class RestEndpointsController {
  constructor(private readonly restEndpointsService: RestEndpointsService) {}

  @UseGuards(JwtAuthGuard) // This extra line
  @Get(':id')
  pingOne(@Param('id') id: string) {
    return this.restEndpointsService.pingOne(id);
  }
}

```

And that's it! If we run 

```bash
$ curl http://localhost:3000/rest-endpoints/12345
```
We'll get a 

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

But when we add a Bearer Token from Clerk

```bash
$ curl -H "Authorization: Bearer <CLERK_BEARER_TOKEN_HERE>" http://localhost:3000/rest-endpoints/12345
```

We get

```
REST Ping #12345!
```


If we try it out with our GraphQL Endpoint

```ts
// src/graphql-endpoints/graphql-endpoints.resolver.ts
import { Resolver, Query, Args } from '@nestjs/graphql';
import { GraphqlEndpointsService } from './graphql-endpoints.service';
import { GraphqlEndpoint } from './entities/graphql-endpoint.entity';
import { UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from 'src/authentication/jwt-auth.guard';

@Resolver(() => GraphqlEndpoint)
export class GraphqlEndpointsResolver {
  constructor(
    private readonly graphqlEndpointsService: GraphqlEndpointsService,
  ) {}

  @UseGuards(JwtAuthGuard)
  @Query(() => String, { name: 'pingOne' })
  pingOne(@Args('id') id: string) {
    return this.graphqlEndpointsService.pingOne(id);
  }
}

```

We get the expected behaviour!


![Testing GraphQL with Clerk JWT](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gvn1lnd1wdy1ikb69gp6.png)


And that's it! Hope this was useful. I'll be posting more about Clerk and NestJs in the coming weeks so if ur into that follow me on Twitter
https://twitter.com/RobertoYamanaka

---

## NOTE: Getting a Bearer Token from Clerk

You can find your Clerk Bearer token under `localstorage` in your frontend as `clerk-db-jwt`

---

## Github Repo

https://github.com/robertoyamanaka/clerk-w-nestjs