mancala/apps/backend/tests/auth/KeycloakTokenVerifier.test.ts

62 lines
3.0 KiB
TypeScript
Raw Normal View History

import { createLocalJWKSet, exportJWK, generateKeyPair, JWK, JWTPayload, KeyLike, SignJWT } from "jose";
import { InvalidTokenError, KeycloakTokenVerifier } from "../../src/auth";
const ISSUER = "http://keycloak.test/realms/mancala";
describe("KeycloakTokenVerifier", () => {
let privateKey: KeyLike;
let otherPrivateKey: KeyLike;
let verifier: KeycloakTokenVerifier;
beforeAll(async () => {
const keyPair = await generateKeyPair("RS256");
privateKey = keyPair.privateKey;
otherPrivateKey = (await generateKeyPair("RS256")).privateKey;
const publicJwk: JWK = { ...(await exportJWK(keyPair.publicKey)), kid: "test-key", alg: "RS256" };
verifier = new KeycloakTokenVerifier({
issuer: ISSUER,
allowedClientIds: ["mancala-web"],
keys: createLocalJWKSet({ keys: [publicJwk] }),
});
});
const sign = (claims: JWTPayload, options: { key?: KeyLike, expiresIn?: string, issuer?: string } = {}) =>
new SignJWT({ typ: "Bearer", azp: "mancala-web", preferred_username: "alice", ...claims })
.setProtectedHeader({ alg: "RS256", kid: "test-key" })
.setSubject("user-1")
.setIssuer(options.issuer ?? ISSUER)
.setIssuedAt()
.setExpirationTime(options.expiresIn ?? "5m")
.sign(options.key ?? privateKey);
it("returns the user of a valid access token", async () => {
const user = await verifier.verify(await sign({ realm_access: { roles: ["offline_access"] } }));
expect(user).toEqual({ id: "kc:user-1", name: "alice", isGuest: false });
});
it("marks users with the guest role as guests", async () => {
const user = await verifier.verify(await sign({ preferred_username: "guest-1a2b3c4d", realm_access: { roles: ["guest"] } }));
expect(user).toEqual({ id: "kc:user-1", name: "guest-1a2b3c4d", isGuest: true });
});
it.each([
["signed with another key", () => sign({}, { key: otherPrivateKey })],
["from another issuer", () => sign({}, { issuer: "http://evil.test/realms/mancala" })],
["expired", () => sign({}, { expiresIn: "-1m" })],
["an ID token", () => sign({ typ: "ID" })],
["a refresh token", () => sign({ typ: "Refresh" })],
["issued to another client", () => sign({ azp: "some-other-app" })],
["not a JWT", async () => "not-a-token"],
])("rejects a token %s", async (_, makeToken) => {
await expect(verifier.verify(await makeToken())).rejects.toBeInstanceOf(InvalidTokenError);
});
it("rejects unsigned tokens", async () => {
const header = Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url");
const payload = Buffer.from(JSON.stringify({
iss: ISSUER, sub: "user-1", typ: "Bearer", azp: "mancala-web", exp: Math.floor(Date.now() / 1000) + 300,
})).toString("base64url");
await expect(verifier.verify(`${header}.${payload}.`)).rejects.toBeInstanceOf(InvalidTokenError);
});
});