Node.js with TSOA and Authentification - (Part 4) Header-Validiation in AuthMiddleware
We'll focus on authentication middleware. We'll examine the request header and handle missing or incorrect information. For such problems, we'll create appropriate error handling. Let's get started!
This article focuses on verifying the incoming request. In case of an error – meaning that neither an Authorization header nor a syntactically correct header was provided – the application's response should be "StatusCode 401 Unauthenticated". Before we begin, it's recommended that you read the previous articles in this series:
Preparing the Error Class
I've already described the necessary steps in a previous article: Node.js and My Base Error Class. We derive our UnauthenticatedError from this BaseError class. If you have followed the steps in the article, you should have the following results:
-
A new BaseError class.
-
A StringUtils file containing a toString function and the error prototype definition for toJSON.
-
The NPM package
flattedinstalled.
Next, we create the UnauthenticatedError with a 401 status code. This error indicates that we could not determine if the request was made by the person claiming to be authorized. This contrasts with the UnauthorizedError, where access is denied to the verified user.
import BaseError, { BaseErrorOptions } from '@/errors/BaseError'
class UnauthenticatedError extends BaseError {
constructor(options: BaseErrorOptions) {
const { detail = 'Unauthenticated', cause } = options
super({ detail, cause, statusCode: 401 })
}
public toJSON() {
return super.toJSON()
}
public toString() {
return JSON.stringify(this.toJSON())
}
}
export default UnauthenticatedError
Authorization Header Verification
Now we turn our attention to the AuthMiddleware. We create a validateHeader function that checks the header and extracts the token from it. It is important to emphasize that we must not throw an error, but rather call a Promise.reject(). Otherwise, we will break out of the Express middleware.
import { Request } from 'express'
import UnauthenticatedError from '@/errors/UnauthenticatedError'
type ValidateHeaderSucceed = {
isValid: true
bearerToken: string
}
type ValidateHeaderFailed = {
isValid: false
error: UnauthenticatedError
}
type ValidateHeaderResult = ValidateHeaderSucceed | ValidateHeaderFailed
const validateHeader = (request: Request): ValidateHeaderResult => {
// look up header for authorization
const authHeader = request.headers.authorization
// validate authorization header
if (!authHeader) {
return {
isValid: false,
error: new UnauthenticatedError({ detail: 'No authorization header provided' }),
}
}
if (!authHeader.startsWith('Bearer ')) {
return {
isValid: false,
error: new UnauthenticatedError({ detail: 'No bearer token provided' }),
}
}
const bearerToken = authHeader.split(' ')[1]
if (!bearerToken) {
return {
isValid: false,
error: new UnauthenticatedError({ detail: 'No bearer token provided' }),
}
}
return {
isValid: true,
bearerToken,
}
}
The integration takes place in the AuthMiddleware, where we call Promise.reject in case of an error.
const expressAuthentication = (
request: Request,
securityName: string,
requiredScopes?: string[],
): Promise<AuthResultType> => {
const validateHeaderResult = validateHeader(request)
if (!validateHeaderResult.isValid) {
console.log(`[AuthMiddleware::${securityName}] Failed due to: ${validateHeaderResult.error.message}`)
return Promise.reject(validateHeaderResult.error)
}
// ... here we will check the token
}
Conclusion
At this point, we've succeeded. We've checked whether an incoming request has set the Authorization header. This completes the first step towards authentication. Next, we need to verify the token. Before that, however, I want to address the Promise.reject. For this, we need error middleware that translates such errors into a standard response.