Skip to content

Commit

Permalink
ENG-1237: Support edge runtime compatibility (#1)
Browse files Browse the repository at this point in the history
* Use jose instead of jwks-rsa.  Still need to verify nonce.

* Verify nonce manually since there is no built-in support in jose

* Replace jsonwebtoken by jose too

* Mark as 3.0.1-jose version

* Reset to previous package version

* Ignore vscode settings

* Use jose's decodeProjectedHeader for alg and kid

* Fix tests
  • Loading branch information
kaaloo authored Jun 18, 2023
1 parent c2cd5c3 commit 6601b13
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 37 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules/*
package-lock.json
dist
coverage
.vscode/*
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
"url": "https://github.com/stefan-prokop-cz/verify-apple-id-token/issues"
},
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jwks-rsa": "^3.0.0"
"jose": "^4.14.4"
},
"devDependencies": {
"@types/jest": "29.5.2",
Expand All @@ -58,4 +57,4 @@
"ts-jest": "26.5.6",
"typescript": "4.9.5"
}
}
}
51 changes: 19 additions & 32 deletions src/lib/verifyAppleIdToken.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,34 @@
import * as jwt from "jsonwebtoken";
import * as jwksClient from "jwks-rsa";
import { VerifyAppleIdTokenParams, VerifyAppleIdTokenResponse } from "./types";
import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
import { VerifyAppleIdTokenParams } from "./types";

export const APPLE_BASE_URL = "https://appleid.apple.com";
export const JWKS_APPLE_URI = "/auth/keys";

export const getApplePublicKey = async (kid: string) => {
const client = jwksClient({
cache: true,
jwksUri: `${APPLE_BASE_URL}${JWKS_APPLE_URI}`,
export const getApplePublicKey = async (kid: string, alg: string) => {
const JWKS = createRemoteJWKSet(new URL(`${APPLE_BASE_URL}${JWKS_APPLE_URI}`));
const key = await JWKS({
alg,
kid,
});
const key = await new Promise<jwksClient.SigningKey>((resolve, reject) => {
client.getSigningKey(kid, (error, result) => {
if (error) {
return reject(error);
}
return resolve(result);
});
});
return key.getPublicKey();
return key;
};

export const verifyToken = async (params: VerifyAppleIdTokenParams) => {
const decoded = jwt.decode(params.idToken, { complete: true });
const { kid, alg } = decoded.header;
const { alg, kid } = decodeProtectedHeader(params.idToken);

const applePublicKey = await getApplePublicKey(kid);
const jwtClaims = jwt.verify(params.idToken, applePublicKey, {
algorithms: [alg as jwt.Algorithm],
nonce: params.nonce,
}) as VerifyAppleIdTokenResponse;
const applePublicKey = await getApplePublicKey(kid, alg);

const { payload: jwtClaims } = await jwtVerify(params.idToken, applePublicKey);

if (jwtClaims?.nonce !== params.nonce) {
throw new Error(`The nonce parameter does not match - nonce: ${jwtClaims.nonce} | expected: ${params.nonce}`);
}

if (jwtClaims?.iss !== APPLE_BASE_URL) {
throw new Error(
`The iss does not match the Apple URL - iss: ${jwtClaims.iss} | expected: ${APPLE_BASE_URL}`
);
throw new Error(`The iss does not match the Apple URL - iss: ${jwtClaims.iss} | expected: ${APPLE_BASE_URL}`);
}

const isFounded = []
.concat(jwtClaims.aud)
.some((aud) => [].concat(params.clientId).includes(aud));
const isFounded = [].concat(jwtClaims.aud).some((aud) => [].concat(params.clientId).includes(aud));

if (isFounded) {
["email_verified", "is_private_email"].forEach((field) => {
Expand All @@ -51,7 +40,5 @@ export const verifyToken = async (params: VerifyAppleIdTokenParams) => {
return jwtClaims;
}

throw new Error(
`The aud parameter does not include this client - is: ${jwtClaims.aud} | expected: ${params.clientId}`
);
throw new Error(`The aud parameter does not include this client - is: ${jwtClaims.aud} | expected: ${params.clientId}`);
};
4 changes: 2 additions & 2 deletions src/test/verifyAppleIdToken.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe("Verify Apple idToken", () => {
);
await verifyAppleIdToken({ idToken, clientId });
} catch (error) {
return expect(error.message).toMatch(/jwt expired/);
return expect(error.message).toMatch(/"exp" claim timestamp check failed/);
}
throw new Error("Expected to throw");
});
Expand All @@ -116,7 +116,7 @@ describe("Verify Apple idToken", () => {
);
await verifyAppleIdToken({ idToken, clientId, nonce: "def" });
} catch (error) {
return expect(error.message).toMatch(/jwt nonce invalid/);
return expect(error.message).toMatch(/The nonce parameter does not match/);
}
throw new Error("Expected to throw");
});
Expand Down

0 comments on commit 6601b13

Please sign in to comment.