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); }); });