Node.js with TSOA and Authentication - (Part 3) Create a JWT

In this article, we'll obtain a token to use when calling our endpoints in the future. To achieve this, we'll create an endpoint that generates a valid, signed token. Let's get started!

4 minutes

Before we implement authentication, we need a valid JWT to send to our API. User management alone is complex, so I'll limit myself to the bare minimum: we request a new JWT and receive it in the response.

To put this into context: Depending on the requirements, it may be necessary to register new users, confirm their registrations, and issue a JWT upon successful login. Alternatively, instead of a username and password, you can send a magic link to the registered email address. This email contains a JWT that grants us access to the application.

We generate a JWT and return it in the response. Alternatively, you could send the JWT to the registered email address, providing a passwordless login mechanism. If you'd like me to cover the details in a separate article series, leave a comment.

Before we begin, we recommend reading the previous articles in this series:

Prerequisite

We need the NPM package with the JWT implementation:

npm install jsonwebtoken
npm install -D @types/jsonwebtoken

Furthermore, an asymmetric authentication method is used to sign the JWT. In our Node.js application, we use the private key to generate tokens. We can share the public key with third parties (e.g., our frontend) to verify our tokens.

For our purposes, you can create the key pair as follows:

openssl genrsa -out jwt 4096
openssl rsa -in jwt -pubout -outform PEM -out jwt.pub

The first command generates the private key named jwt. The second command generates the corresponding public key named jwt.pub. Place both files in the project's root directory, "certs".

Endpoint for JWT Query

For the query, we create a GET endpoint that performs the necessary steps in the controller:

  • Definition of the JWT data
  • Reading the private key
  • Definition of the signature options
import { Controller } from '@tsoa/runtime'  
import { Get, Route } from 'tsoa'  
import fs from 'node:fs'  
import path from 'node:path'  
import jwt from 'jsonwebtoken'  

@Route('users')  
export class UserController extends Controller {  
  @Get('jwt')  
  public async getJwt (): Promise<string> {  
    const jwtData = {  
      userId: 'user-uuid',  
      email: 'user.mail@example.com',  
      scopes: ['ADMIN'],  
      name: `Test User`,  
    }  

    const privateKey = fs.readFileSync(path.join(path.resolve('certs'), 'jwt'), 'utf8')  

    const durationInSeconds = 60 * 60 * 24 // 24 hours  
    const signOptions = {  
      issuer: 'demo-company',  
      subject: 'user-uuid',  
      audience: 'https://demo-company.de',  
      expiresIn: durationInSeconds,  
      algorithm: 'RS256' as jwt.Algorithm,  
    }  

    return jwt.sign(jwtData, privateKey, signOptions)  
  }  
}

If you'd like to learn more about JWT, I'd be happy to cover it in a separate article. Leave me a comment here or

Book a call

The new route will be available immediately after restarting the application using npm run start:dev. Note: If you're working on the route definitions while the start:dev watch is listening, the route.ts file won't update. Unfortunately, I haven't found a good solution for this yet. Even the GitHub entry on this topic isn't helpful.

You can now retrieve the token:

curl http://localhost:4000/users/jwt

The response is the JWT, which you can enter on the website https://jwt.io to view the result. You can verify the token's authenticity by entering a public key. To do this, copy the contents of the file certs/jwt.pub into the corresponding section. If it matches, the indicator will turn green.

Conclusion

With this step, we have a JWT that we can use when calling the EntityController. We use the Authorisation header for this. Using curl, the calls would look like this:

DEMO_TOKEN=$(curl -s http://localhost:4000/users/jwt)

The token is cached and then reused directly.

curl -H "Authorization: Bearer ${DEMO_TOKEN}" http://localhost:4000/entities

In our AuthMiddleware, we access it via request.headers['authorization']. We will continue from this point in the following article.

call to action background image

Subscribe to my newsletter

Receive once a month news from the areas of software development and communication peppered with book and link recommendations.