test(ap1): demonstrate token storage tradeoff
This commit is contained in:
+40
-12
@@ -36,8 +36,32 @@ try {
|
||||
assert.equal(authorizationUrl?.searchParams.get("code_challenge_method"), "S256");
|
||||
assert.ok(authorizationUrl?.searchParams.get("code_challenge"));
|
||||
|
||||
const accessToken = await page.evaluate(() => window.__pattern1.getAccessToken());
|
||||
assert.ok(accessToken, "access token must exist in browser memory");
|
||||
await page.evaluate(() => {
|
||||
const originalFetch = window.fetch.bind(window);
|
||||
window.__xssProbe = { authorization: null };
|
||||
window.fetch = (input, init = {}) => {
|
||||
const headers = new Headers(
|
||||
init.headers ?? (input instanceof Request ? input.headers : undefined),
|
||||
);
|
||||
const authorization = headers.get("Authorization");
|
||||
if (authorization) {
|
||||
window.__xssProbe.authorization = authorization;
|
||||
}
|
||||
return originalFetch(input, init);
|
||||
};
|
||||
});
|
||||
|
||||
await page.locator("#call-api").click();
|
||||
await page.waitForFunction(() => {
|
||||
const text = document.querySelector("#result")?.textContent ?? "";
|
||||
return text.includes('"httpStatus": 200');
|
||||
});
|
||||
|
||||
const capturedAuthorization = await page.evaluate(
|
||||
() => window.__xssProbe.authorization,
|
||||
);
|
||||
assert.match(capturedAuthorization, /^Bearer /u);
|
||||
const accessToken = capturedAuthorization.slice("Bearer ".length);
|
||||
const payload = JSON.parse(
|
||||
Buffer.from(accessToken.split(".")[1], "base64url").toString("utf8"),
|
||||
);
|
||||
@@ -56,12 +80,10 @@ try {
|
||||
false,
|
||||
"access token must not be persisted in Web Storage",
|
||||
);
|
||||
|
||||
await page.locator("#call-api").click();
|
||||
await page.waitForFunction(() => {
|
||||
const text = document.querySelector("#result")?.textContent ?? "";
|
||||
return text.includes('"httpStatus": 200');
|
||||
});
|
||||
assert.ok(
|
||||
capturedAuthorization,
|
||||
"runtime XSS-style fetch hooking can still observe a memory-only bearer token",
|
||||
);
|
||||
|
||||
if (process.env.WRONG_AUDIENCE_URL) {
|
||||
const response = await fetch(process.env.WRONG_AUDIENCE_URL, {
|
||||
@@ -88,13 +110,19 @@ try {
|
||||
await page.reload();
|
||||
await page.locator('[data-authenticated="false"]').waitFor();
|
||||
assert.equal(
|
||||
await page.evaluate(() => window.__pattern1.getAccessToken()),
|
||||
null,
|
||||
"reload must clear the memory-only token",
|
||||
await page.evaluate(
|
||||
(token) => JSON.stringify({
|
||||
localStorage: Object.values(localStorage),
|
||||
sessionStorage: Object.values(sessionStorage),
|
||||
}).includes(token),
|
||||
accessToken,
|
||||
),
|
||||
false,
|
||||
"reload must clear the memory-only token without persisting it",
|
||||
);
|
||||
|
||||
console.log(
|
||||
"pattern1 browser verified: code+PKCE S256, audience/issuer negatives 401, Web Storage token 0, reload clears token",
|
||||
"pattern1 browser verified: PKCE, audience/issuer negatives, persistent token 0, runtime fetch hook observes bearer, reload clears token",
|
||||
);
|
||||
} finally {
|
||||
await browser.close();
|
||||
|
||||
Reference in New Issue
Block a user