Skip to content

Commit e707770

Browse files
authored
Merge commit from fork
getToken() called decodeURIComponent() on the Authorization Bearer value outside any error handling, so a request with a malformed percent-encoded token (e.g. `Authorization: Bearer %`) threw an uncaught URIError to the caller. Treat malformed encodings as an invalid token and return null, matching how undecodable tokens are already handled. Fixes GHSA-xmf8-cvqr-rfgj
1 parent 53c7e2f commit e707770

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

packages/core/src/jwt.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,12 @@ export async function getToken(
172172

173173
if (!token && authorizationHeader?.split(" ")[0] === "Bearer") {
174174
const urlEncodedToken = authorizationHeader.split(" ")[1]
175-
token = decodeURIComponent(urlEncodedToken)
175+
try {
176+
token = decodeURIComponent(urlEncodedToken)
177+
} catch {
178+
// Malformed percent-encoding makes the Bearer token invalid
179+
return null
180+
}
176181
}
177182

178183
if (!token) return null

packages/core/test/jwt.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, it, expect } from "vitest"
22
import { encode, decode } from "../jwt"
3+
import { getToken } from "../src/jwt"
34

45
describe("supports secret rotation", () => {
56
const token = { foo: "bar" }
@@ -38,3 +39,35 @@ describe("supports secret rotation", () => {
3839
)
3940
})
4041
})
42+
43+
describe("getToken", () => {
44+
const secret = "secret"
45+
46+
it("returns null for a malformed percent-encoded Bearer token", async () => {
47+
for (const value of ["%", "%A", "%ZZ"]) {
48+
const req = {
49+
headers: new Headers({ authorization: `Bearer ${value}` }),
50+
}
51+
await expect(getToken({ req, secret })).resolves.toBeNull()
52+
}
53+
})
54+
55+
it("returns null for a malformed Bearer token when raw is set", async () => {
56+
const req = {
57+
headers: new Headers({ authorization: "Bearer %" }),
58+
}
59+
await expect(getToken({ req, secret, raw: true })).resolves.toBeNull()
60+
})
61+
62+
it("still reads a valid Bearer token", async () => {
63+
const salt = "authjs.session-token"
64+
const jwt = await encode({ salt, token: { foo: "bar" }, secret })
65+
const req = {
66+
headers: new Headers({
67+
authorization: `Bearer ${encodeURIComponent(jwt)}`,
68+
}),
69+
}
70+
const decoded = await getToken({ req, secret })
71+
expect(decoded?.foo).toEqual("bar")
72+
})
73+
})

0 commit comments

Comments
 (0)