Node.js mit TSOA und Authentifizierung - (Teil 5) Error-Middleware für standardkonforme Antworten

Nachdem wir in der Authentifizierungsmiddleware erfolgreich Fehler behandelt haben, werden wir eine zusätzliche Middleware einführen, die die Ausgabe standardkonform handhabt. Ich habe das ähnlich für „InvalidParameter“ bereits ausgearbeitet und wende es auf unseren konkreten Fall an. Auf geht’s!

4 Minuten

In diesem Artikel beschäftigen wir uns mit der Fehlersituation bei der Header-Validierung. Das Ziel ist, ein RFC-konformes ProblemDocument zurückzugeben, wenn während der Ausführung ein Fehler auftritt. Wir übersetzen heute den „Unauthenticated-Error“ in eine „application/problem+json“-Antwort. Bevor wir anfangen, empfiehlt es sich, die Vorgänger der Artikelserie zu lesen:

RFC-konforme Antwort generieren

Wenn wir die Applikation im aktuellen Zustand ausführen und im Browser aufrufen (also ohne Authorization-Header), erhalten wir einen Statuscode 401 mit Content-Type text/html. Wünschenswert wäre eine JSON-Antwort.

Ich habe das Vorgehen dafür in einem vergangenen Artikel für InvalidParams mit TSOA beschrieben: Node.js und RFC-konforme Fehlermeldungen. Wir wenden das jetzt auf den hier definierten „Unauthenticated-Error“ an. Voraussetzung hierfür ist die Installation zweier NPM-Pakete.

npm install http-problem-details http-problem-details-mapper

Mithilfe der beiden Pakete gehen wir wie folgt vor:

  1. Wir benötigen einen Mapper unserer Fehlerklasse in ein ProblemDocument aus dem Paket http-problem-details
  2. Wir definieren eine MappingStrategy auf Basis des Pakets http-problem-details-mapper.
  3. Wir erstellen eine Express-Error-Middleware, die die Strategie und das Mapping anwendet.
  4. Wir hängen die Middleware in unsere Express-Applikation.

1. Das Fehlerklassen-Mapping

Wir ergänzen die Error-Klasse um einen Mapper.

export class UnauthenticatedMapper extends ErrorMapper {  
  constructor() {  
    super(UnauthenticatedError)  
  }  

  mapError(error: UnauthenticatedError) {  
    return new ProblemDocument({  
      status: error.statusCode,  
      title: 'Bad request',  
      type: 'unauthenticated',  
      detail: error.detail,  
    })  
  }  
}

2. Die Mapping-Strategie definieren

Hierzu bedienen wir uns der installierten NPM-Pakete und registrieren unser Mapping aus Schritt 1.

import { DefaultMappingStrategy, MapperRegistry } from 'http-problem-details-mapper'
import { UnauthenticatedMapper } from '@/errors/UnauthenticatedError'

const mapperRegistry = new MapperRegistry()  
  .registerMapper(new UnauthenticatedMapper())  
const ErrorMappingStrategy = new DefaultMappingStrategy(mapperRegistry)

3. Die Express-Error-Middleware

Die Error-Middleware muss als Letztes in die Express-Applikation eingebunden werden. Wenn sich der Fehler anhand der Strategie aus Schritt 2 übersetzen lässt, passen wir die Antwort an, ansonsten ignorieren wir ihn.

import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'  
import { IMappingStrategy } from 'http-problem-details-mapper'  

interface HttpProblemResponseOptions {  
  strategy: IMappingStrategy  
}  

function HttpProblemResponse(options: HttpProblemResponseOptions): ErrorRequestHandler {  
  const { strategy } = options  

  return function HttpProblemDetailsMiddleware(  
    error: Error,  
    _request: Request,  
    response: Response,  
    next: NextFunction,  
  ): void {  
    const problem = strategy.map(error)  

    // in case we cannot map the error, we will not handle it  
    if (!problem) {  
      next(error)  
      return  
    }  

    response.statusCode = problem.status  
    response.setHeader('Content-Type', 'application/problem+json')  
    response.json(problem)  
  }  
}  

export default HttpProblemResponse

4. Einbindung in Express

Als letzter Schritt bleibt die Einbindung in Express. Dazu nehmen wir das Mapping aus Schritt 2 und geben es als Parameter an die Middleware aus Schritt 3 weiter. Damit sieht unsere Express-Applikation wie folgt aus:

import express from 'express'  
import { RegisterRoutes } from '@/routes'  
import { DefaultMappingStrategy, MapperRegistry } from 'http-problem-details-mapper'  
import HttpProblemResponse from '@/errors/HttpProblemResponse'  
import { UnauthenticatedMapper } from '@/errors/UnauthenticatedError'  

const PORT = process.env.PORT || 4000  

const mapperRegistry = new MapperRegistry()  
  .registerMapper(new UnauthenticatedMapper())  
const ErrorMappingStrategy = new DefaultMappingStrategy(mapperRegistry)  

const app = express()  
RegisterRoutes(app)  

// The RFC 7807 error middleware has to be the last app.use() to work as intended  
// It will map the thrown errors to the RFC applying responses  
app.use(HttpProblemResponse({ strategy: ErrorMappingStrategy }))  

app.listen(PORT, () => {  
  console.log(`Server is listening on port ${PORT}`)  
})

Fazit

Die aktuelle Applikation funktioniert im Fehlerfall vorbildlich. Bei einem Aufruf ohne Authorization-Header erhalten wir eine RFC-konforme JSON-Antwort. Ein testweiser Aufruf:

curl http://localhost:4000/entities

liefert uns wie erwartet eine passende Antwort:

{
  "type": "unauthenticated",
  "title": "Bad request",
  "detail": "No authorization header provided",
  "status": 401
}

Damit ist der Weg frei, um uns um das Token und dessen Überprüfung zu kümmern. Das Herzstück der Implementierung findest du in Teil 6: Tokenvalidierung.

call to action background image

Abonniere meinen Newsletter

Erhalte einmal im Monat Nachrichten aus den Bereichen Softwareentwicklung und Kommunikation gespikt mit Buch- und Linkempfehlungen.