From 8aba403d57e805c5ffa3c0c430c081d0167d47d4 Mon Sep 17 00:00:00 2001 From: Adam Shiervani Date: Sat, 19 Sep 2026 15:16:36 +0200 Subject: [PATCH] fix(auth): accept the bearer scheme in any case Scheme names are case-insensitive (RFC 9110). The token is still compared exactly. --- src/auth.ts | 3 ++- test/auth.test.ts | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 5856cca..96c895d 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -64,7 +64,8 @@ const sha256 = (value: string) => createHash("sha256").update(value).digest(); export const bearerToken = (expected: string) => { const expectedDigest = sha256(expected); return (req: Request, res: Response, next: NextFunction) => { - const presented = req.headers.authorization?.match(/^Bearer (.+)$/)?.[1]; + // The scheme name is case-insensitive (RFC 9110); the token is not. + const presented = req.headers.authorization?.match(/^Bearer +(.+)$/i)?.[1]; if (!presented || !timingSafeEqual(sha256(presented), expectedDigest)) { throw new UnauthorizedError("Invalid bearer token"); } diff --git a/test/auth.test.ts b/test/auth.test.ts index 2adcc51..d324c3c 100644 --- a/test/auth.test.ts +++ b/test/auth.test.ts @@ -12,17 +12,21 @@ describe("bearerToken", () => { return { headers: { authorization } } as unknown as Request; } - it("calls next for the configured token", () => { - const next = vi.fn(); - guard(request("Bearer release-sync-secret"), res, next); - expect(next).toHaveBeenCalledOnce(); - }); + it.each(["Bearer release-sync-secret", "bearer release-sync-secret", "BEARER release-sync-secret"])( + "calls next for %j", + authorization => { + const next = vi.fn(); + guard(request(authorization), res, next); + expect(next).toHaveBeenCalledOnce(); + }, + ); it.each([ ["no header", undefined], ["wrong token", "Bearer nope"], ["missing scheme", "release-sync-secret"], ["prefix of the token", "Bearer release-sync"], + ["token in a different case", "Bearer RELEASE-SYNC-SECRET"], ])("rejects %s without calling next", (_label, authorization) => { const next = vi.fn(); expect(() => guard(request(authorization), res, next)).toThrow(UnauthorizedError);