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!
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 callThe 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:
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.