Node.js mit TSOA und Authentifizierung - (Teil 3) Ein JWT erzeugen
In diesem Artikel besorgen wir uns ein Token, mit dem wir unsere Endpunkte künftig aufrufen. Dazu erstellen wir einen Endpunkt, der ein gültiges, signiertes Token erzeugt. Packen wir es an!
Bevor wir die Authentifizierung implementieren, benötigen wir ein gültiges JWT, das wir an unsere Schnittstelle übermitteln. Das Thema Nutzerverwaltung allein ist umfangreich, weswegen ich mich auf das absolute Minimum beschränke: Wir fragen ein neues JWT an und erhalten es als Antwort.
Um das in einen sinnvollen Kontext zu bringen: Je nachdem, wie die Anforderung ist, ist es notwendig, neue Nutzer:innen zu registrieren, dies zu bestätigen und beim erfolgreichen Login das JWT zu übermitteln. Alternativ zu Nutzernamen und Passwort kann man einen Magic-Link an die registrierte E-Mail-Adresse senden. Diese E-Mail enthält einen JWT, mit dem wir die Applikation nutzen können.
Wir erstellen ein JWT und geben es als Antwort zurück. Stattdessen könntest du das JWT an die registrierte E-Mail-Adresse schicken und hättest einen Loginmechanismus, bei dem deine Nutzenden kein Passwort benötigen. Wenn ich die Details in einer separaten Artikelserie aufarbeiten soll, schreib mir einen Kommentar.
Bevor wir anfangen, empfiehlt es sich, die Vorgänger der Artikelserie zu lesen:
- Teil 1: Die Zielsetzung
- Teil 2: Das Basis-Setup
Vorbedingung
Wir benötigen das NPM-Paket mit der JWT-Implementierung:
npm install jsonwebtoken
npm install -D @types/jsonwebtoken
Des Weiteren wird zur Signierung des JWTs ein asymmetrisches Verfahren verwendet. In unserer Node.js-Applikation verwenden wir den privaten Schlüssel bei der Erstellung. Den öffentlichen Schlüssel können wir zur Prüfung unserer Token an Dritte (z. B. unser Frontend) weitergeben.
Für unseren Fall kannst du das Schlüsselpaar wie folgt erstellen:
openssl genrsa -out jwt 4096
openssl rsa -in jwt -pubout -outform PEM -out jwt.pub
Der erste Befehl erzeugt den privaten Schlüssel namens jwt. Der zweite Befehl generiert daraus den passenden öffentlichen Teil namens jwt.pub. Diese beiden Dateien legst du im Projekt-Root-Verzeichnis „certs“ ab.
Endpunkt für JWT-Abfrage
Für die Abfrage erstellen wir uns einen GET-Endpunkt, der im Controller die notwendigen Schritte durchführt:
- Definition der JWT-Daten
- Einlesen des privaten Schlüssels
- Definition der Signaturoptionen
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)
}
}
Wenn du mehr über JWT erfahren möchtest, arbeite ich das gerne in einem separaten Artikel auf. Lass mir hier einen Kommentar oder
Schnapp dir einen TerminMit einem Neustart der Applikation über npm run start:dev ist die neue Route direkt verfügbar. Achtung: Wenn du an den Routendefinitionen arbeitest, während das Watch von start:dev lauscht, aktualisiert sich die Datei route.ts nicht. Dazu habe ich leider noch keinen schlauen Ansatz gefunden. Selbst der GitHub-Eintrag dazu ist nicht hilfreich
Das Token kannst du jetzt abfragen:
Die Antwort ist das JWT, das du auf der Webseite https://jwt.io eingeben kannst, um dir das Ergebnis anzusehen. Du wirst sehen, dass mit der Eingabe eines öffentlichen Schlüssels die Echtheit des Tokens geprüft werden kann → hierfür kopiere den Inhalt der Datei certs/jwt.pub in den entsprechenden Bereich. Wenn es passt, wird die Anzeige grün.
Fazit
Mit diesem Schritt haben wir ein JWT, das wir beim Aufruf des EntityControllers verwenden können. Dafür verwenden wir den Authorization-Header. Mittels curl sähen die Aufrufe wie folgt aus:
DEMO_TOKEN=$(curl -s http://localhost:4000/users/jwt)
Das Token wird zwischengespeichert und anschließend direkt wiederverwendet.
curl -H "Authorization: Bearer ${DEMO_TOKEN}" http://localhost:4000/entities
In unserer AuthMiddleware haben wir über request.headers['authorization'] Zugriff darauf. An genau dieser Stelle machen wir im nächsten Artikel weiter.