summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortimonson <54777088+timonson@users.noreply.github.com>2020-10-20 05:08:34 +0200
committerGitHub <noreply@github.com>2020-10-20 14:08:34 +1100
commit034ab48086557af00216ffe311c71ad4eb0ec4d5 (patch)
tree292abc572ed68eb52c1bc773e64f820497e065df
parent992c2a436e5fe371807dd43bd293bb811fd529e7 (diff)
feat(std/jwt): add a JSON Web Token library (#7991)
Co-authored-by: Tim Reichen <timreichen@users.noreply.github.com>
-rw-r--r--std/jwt/README.md90
-rw-r--r--std/jwt/_algorithm.ts17
-rw-r--r--std/jwt/_algorithm_test.ts11
-rw-r--r--std/jwt/_signature.ts63
-rw-r--r--std/jwt/_signature_test.ts46
-rw-r--r--std/jwt/mod.ts208
-rw-r--r--std/jwt/test.ts304
7 files changed, 739 insertions, 0 deletions
diff --git a/std/jwt/README.md b/std/jwt/README.md
new file mode 100644
index 000000000..95d849d52
--- /dev/null
+++ b/std/jwt/README.md
@@ -0,0 +1,90 @@
+# jwt
+
+Create and verify JSON Web Tokens.
+
+## JSON Web Token
+
+### create
+
+Takes a `payload`, `key` and `header` and returns the url-safe encoded `token`.
+
+```typescript
+import { create } from "https://deno.land/std/token/mod.ts";
+
+const payload = { foo: "bar" };
+const key = "secret";
+
+const token = await create(payload, key); // eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4i-Q1Y0oDZunLgaorkqbYNcNfn5CgdF49UvJ7dUQ4GVTQvpsMLHABkZBWp9sghy3qVOsec6hOcu4RnbFkS30zQ
+```
+
+**Specific algorithm**
+
+```typescript
+const token = await create(payload, key, { header: { alg: "HS256" } });
+```
+
+### verify
+
+Takes a `token`, `key` and an optional `options` object and returns the
+`payload` of the `token` if the `token` is valid. Otherwise it throws an
+`Error`.
+
+```typescript
+import { verify } from "https://deno.land/std/token/mod.ts";
+
+const token =
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4i-Q1Y0oDZunLgaorkqbYNcNfn5CgdF49UvJ7dUQ4GVTQvpsMLHABkZBWp9sghy3qVOsec6hOcu4RnbFkS30zQ";
+const key = "secret";
+
+const payload = await verify(token, key); // { foo: "bar" }
+```
+
+**Specific algorithm**
+
+```ts
+const payload = await verify(token, key, { algorithm: "HS256" });
+```
+
+### decode
+
+Takes a `token` to return an object with the `header`, `payload` and `signature`
+properties if the `token` is valid. Otherwise it throws an `Error`.
+
+```typescript
+import { decode } from "https://deno.land/std/token/mod.ts";
+
+const token =
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4i-Q1Y0oDZunLgaorkqbYNcNfn5CgdF49UvJ7dUQ4GVTQvpsMLHABkZBWp9sghy3qVOsec6hOcu4RnbFkS30zQ";
+
+const { payload, signature, header } = await decode(token); // { header: { alg: "HS512", typ: "JWT" }, payload: { foo: "bar" }, signature: "e22f90d58d280d9ba72e06a8ae4a9b60d70d7e7e4281d178f54bc9edd510e0655342fa6c30b1c00646415a9f6c821cb7a953ac79cea139cbb84676c5912df4cd" }
+```
+
+## Expiration
+
+The optional **exp** claim in the payload (number of seconds since January 1,
+1970, 00:00:00 UTC) that identifies the expiration time on or after which the
+JWT must not be accepted for processing. This module checks if the current
+date/time is before the expiration date/time listed in the **exp** claim.
+
+```typescript
+const oneHour = 60 * 60;
+const token = await create({ exp: Date.now() + oneHour }, "secret");
+```
+
+## Algorithms
+
+The following signature and MAC algorithms have been implemented:
+
+- HS256 (HMAC SHA-256)
+- HS512 (HMAC SHA-512)
+- none ([_Unsecured JWTs_](https://tools.ietf.org/html/rfc7519#section-6)).
+
+## Serialization
+
+This application uses the JWS Compact Serialization only.
+
+## Specifications
+
+- [JSON Web Token](https://tools.ietf.org/html/rfc7519)
+- [JSON Web Signature](https://www.rfc-editor.org/rfc/rfc7515.html)
+- [JSON Web Algorithms](https://www.rfc-editor.org/rfc/rfc7518.html)
diff --git a/std/jwt/_algorithm.ts b/std/jwt/_algorithm.ts
new file mode 100644
index 000000000..c9c5257e1
--- /dev/null
+++ b/std/jwt/_algorithm.ts
@@ -0,0 +1,17 @@
+/*
+ * JSW §1: Cryptographic algorithms and identifiers for use with this specification
+ * are described in the separate JSON Web Algorithms (JWA) specification:
+ * https://www.rfc-editor.org/rfc/rfc7518
+ */
+export type Algorithm = "none" | "HS256" | "HS512";
+export type AlgorithmInput = Algorithm | Array<Exclude<Algorithm, "none">>;
+/**
+ * Verify the algorithm
+ * @param algorithm as string or multiple algorithms in an array excluding 'none'
+ * @param the algorithm from the jwt header
+ */
+export function verify(algorithm: AlgorithmInput, jwtAlg: string): boolean {
+ return Array.isArray(algorithm)
+ ? (algorithm as string[]).includes(jwtAlg)
+ : algorithm === jwtAlg;
+}
diff --git a/std/jwt/_algorithm_test.ts b/std/jwt/_algorithm_test.ts
new file mode 100644
index 000000000..99583bd79
--- /dev/null
+++ b/std/jwt/_algorithm_test.ts
@@ -0,0 +1,11 @@
+import { assertEquals } from "../testing/asserts.ts";
+
+import { verify as verifyAlgorithm } from "./_algorithm.ts";
+
+Deno.test("[jwt] verify algorithm", function () {
+ assertEquals(verifyAlgorithm("HS512", "HS512"), true);
+ assertEquals(verifyAlgorithm("HS512", "HS256"), false);
+ assertEquals(verifyAlgorithm(["HS512"], "HS512"), true);
+ assertEquals(verifyAlgorithm(["HS256", "HS512"], "HS512"), true);
+ assertEquals(verifyAlgorithm(["HS512"], "HS256"), false);
+});
diff --git a/std/jwt/_signature.ts b/std/jwt/_signature.ts
new file mode 100644
index 000000000..81c1309d1
--- /dev/null
+++ b/std/jwt/_signature.ts
@@ -0,0 +1,63 @@
+import type { Algorithm } from "./_algorithm.ts";
+import { HmacSha256 } from "../hash/sha256.ts";
+import { HmacSha512 } from "../hash/sha512.ts";
+import { encode as convertUint8ArrayToBase64url } from "../encoding/base64url.ts";
+import { decodeString as convertHexToUint8Array } from "../encoding/hex.ts";
+
+export function convertHexToBase64url(input: string): string {
+ return convertUint8ArrayToBase64url(convertHexToUint8Array(input));
+}
+
+function encrypt(
+ algorithm: Algorithm,
+ key: string,
+ message: string,
+): string {
+ switch (algorithm) {
+ case "none":
+ return "";
+ case "HS256":
+ return new HmacSha256(key).update(message).toString();
+ case "HS512":
+ return new HmacSha512(key).update(message).toString();
+ default:
+ throw new RangeError(
+ `The algorithm of '${algorithm}' in the header is not supported.`,
+ );
+ }
+}
+
+/**
+ * Create a signature
+ * @param algorithm
+ * @param key
+ * @param input
+ */
+export async function create(
+ algorithm: Algorithm,
+ key: string,
+ input: string,
+): Promise<string> {
+ return convertHexToBase64url(await encrypt(algorithm, key, input));
+}
+
+/**
+ * Verify a signature
+ * @param signature
+ * @param key
+ * @param alg
+ * @param signingInput
+ */
+export async function verify({
+ signature,
+ key,
+ algorithm,
+ signingInput,
+}: {
+ signature: string;
+ key: string;
+ algorithm: Algorithm;
+ signingInput: string;
+}): Promise<boolean> {
+ return signature === (await encrypt(algorithm, key, signingInput));
+}
diff --git a/std/jwt/_signature_test.ts b/std/jwt/_signature_test.ts
new file mode 100644
index 000000000..c02f0f4bc
--- /dev/null
+++ b/std/jwt/_signature_test.ts
@@ -0,0 +1,46 @@
+import { assertEquals } from "../testing/asserts.ts";
+import { create, decode } from "./mod.ts";
+
+import {
+ convertHexToBase64url,
+ create as createSignature,
+ verify as verifySignature,
+} from "./_signature.ts";
+
+const algorithm = "HS256";
+const key = "m$y-key";
+
+Deno.test("[jwt] create signature", async function () {
+ // https://www.freeformatter.com/hmac-generator.html
+ const computedHmacInHex =
+ "2b9e6619fa7f2c8d8b3565c88365376b75b1b0e5d87e41218066fd1986f2c056";
+ assertEquals(
+ await createSignature(algorithm, key, "thisTextWillBeEncrypted"),
+ convertHexToBase64url(computedHmacInHex),
+ );
+
+ const anotherVerifiedSignatureInBase64Url =
+ "p2KneqJhji8T0PDlVxcG4DROyzTgWXbDhz_mcTVojXo";
+ assertEquals(
+ await createSignature(
+ algorithm,
+ key,
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ",
+ ),
+ anotherVerifiedSignatureInBase64Url,
+ );
+});
+
+Deno.test("[jwt] verify signature", async function () {
+ const jwt = await create({}, key);
+ const { header, signature } = decode(jwt);
+
+ const validSignature = await verifySignature({
+ signature,
+ key,
+ algorithm: header.alg,
+ signingInput: jwt.slice(0, jwt.lastIndexOf(".")),
+ });
+
+ assertEquals(validSignature, true);
+});
diff --git a/std/jwt/mod.ts b/std/jwt/mod.ts
new file mode 100644
index 000000000..09485c8c6
--- /dev/null
+++ b/std/jwt/mod.ts
@@ -0,0 +1,208 @@
+import type { Algorithm, AlgorithmInput } from "./_algorithm.ts";
+import * as base64url from "../encoding/base64url.ts";
+import { encodeToString as convertUint8ArrayToHex } from "../encoding/hex.ts";
+import {
+ create as createSignature,
+ verify as verifySignature,
+} from "./_signature.ts";
+import { verify as verifyAlgorithm } from "./_algorithm.ts";
+
+/*
+ * JWT §4.1: The following Claim Names are registered in the IANA
+ * "JSON Web Token Claims" registry established by Section 10.1. None of the
+ * claims defined below are intended to be mandatory to use or implement in all
+ * cases, but rather they provide a starting point for a set of useful,
+ * interoperable claims.
+ * Applications using JWTs should define which specific claims they use and when
+ * they are required or optional.
+ */
+export interface PayloadObject {
+ iss?: string;
+ sub?: string;
+ aud?: string[] | string;
+ exp?: number;
+ nbf?: number;
+ iat?: number;
+ jti?: string;
+ [key: string]: unknown;
+}
+
+export type Payload = PayloadObject | string;
+
+/*
+ * JWS §4.1.1: The "alg" value is a case-sensitive ASCII string containing a
+ * StringOrURI value. This Header Parameter MUST be present and MUST be
+ * understood and processed by implementations.
+ */
+export interface Header {
+ alg: Algorithm;
+ [key: string]: unknown;
+}
+
+const encoder = new TextEncoder();
+const decoder = new TextDecoder();
+
+/*
+ * JWT §4.1.4: Implementers MAY provide for some small leeway to account for
+ * clock skew.
+ */
+function isExpired(exp: number, leeway = 0): boolean {
+ return exp + leeway < Date.now() / 1000;
+}
+
+function tryToParsePayload(input: string): unknown {
+ try {
+ return JSON.parse(input);
+ } catch {
+ return input;
+ }
+}
+
+/**
+ * Decodes a token into an { header, payload, signature } object.
+ * @param token
+ */
+export function decode(
+ token: string,
+): {
+ header: Header;
+ payload: unknown;
+ signature: string;
+} {
+ const parsedArray = token
+ .split(".")
+ .map(base64url.decode)
+ .map((uint8Array, index) => {
+ switch (index) {
+ case 0:
+ try {
+ return JSON.parse(decoder.decode(uint8Array));
+ } catch {
+ break;
+ }
+ case 1:
+ return tryToParsePayload(decoder.decode(uint8Array));
+ case 2:
+ return convertUint8ArrayToHex(uint8Array);
+ }
+ throw TypeError("The serialization is invalid.");
+ });
+
+ const [header, payload, signature] = parsedArray;
+
+ if (
+ !(
+ (typeof signature === "string" &&
+ typeof header?.alg === "string") && payload?.exp !== undefined
+ ? typeof payload.exp === "number"
+ : true
+ )
+ ) {
+ throw new Error(`The token is invalid.`);
+ }
+
+ if (
+ typeof payload?.exp === "number" &&
+ isExpired(payload.exp)
+ ) {
+ throw RangeError("The token is expired.");
+ }
+
+ return {
+ header,
+ payload,
+ signature,
+ };
+}
+
+export type VerifyOptions = {
+ algorithm?: AlgorithmInput;
+};
+
+/**
+ * Verifies a token.
+ * @param token
+ * @param key
+ * @param object with property 'algorithm'
+ */
+export async function verify(
+ token: string,
+ key: string,
+ { algorithm = "HS512" }: VerifyOptions = {},
+): Promise<unknown> {
+ const { header, payload, signature } = decode(token);
+
+ if (!verifyAlgorithm(algorithm, header.alg)) {
+ throw new Error(
+ `The token's algorithm does not match the specified algorithm '${algorithm}'.`,
+ );
+ }
+
+ /*
+ * JWS §4.1.11: The "crit" (critical) Header Parameter indicates that
+ * extensions to this specification and/or [JWA] are being used that MUST be
+ * understood and processed.
+ */
+ if ("crit" in header) {
+ throw new Error(
+ "The 'crit' header parameter is currently not supported by this module.",
+ );
+ }
+
+ if (
+ !(await verifySignature({
+ signature,
+ key,
+ algorithm: header.alg,
+ signingInput: token.slice(0, token.lastIndexOf(".")),
+ }))
+ ) {
+ throw new Error(
+ "The token's signature does not match the verification signature.",
+ );
+ }
+
+ return payload;
+}
+
+/*
+ * JSW §7.1: The JWS Compact Serialization represents digitally signed or MACed
+ * content as a compact, URL-safe string. This string is:
+ * BASE64URL(UTF8(JWS Protected Header)) || '.' ||
+ * BASE64URL(JWS Payload) || '.' ||
+ * BASE64URL(JWS Signature)
+ */
+function createSigningInput(header: Header, payload: Payload): string {
+ return `${
+ base64url.encode(
+ encoder.encode(JSON.stringify(header)),
+ )
+ }.${
+ base64url.encode(
+ encoder.encode(
+ typeof payload === "string" ? payload : JSON.stringify(payload),
+ ),
+ )
+ }`;
+}
+
+/**
+ * Creates a token.
+ * @param payload
+ * @param key
+ * @param object with property 'header'
+ */
+export async function create(
+ payload: Payload,
+ key: string,
+ {
+ header = { alg: "HS512", typ: "JWT" },
+ }: {
+ header?: Header;
+ } = {},
+): Promise<string> {
+ const signingInput = createSigningInput(header, payload);
+ const signature = await createSignature(header.alg, key, signingInput);
+
+ return `${signingInput}.${signature}`;
+}
diff --git a/std/jwt/test.ts b/std/jwt/test.ts
new file mode 100644
index 000000000..deaa857e2
--- /dev/null
+++ b/std/jwt/test.ts
@@ -0,0 +1,304 @@
+import { create, decode, Header, Payload, verify } from "./mod.ts";
+
+import {
+ assertEquals,
+ assertThrows,
+ assertThrowsAsync,
+} from "../testing/asserts.ts";
+
+const header: Header = {
+ alg: "HS256",
+ typ: "JWT",
+};
+
+const payload: Payload = {
+ name: "John Doe",
+};
+
+const key = "secret";
+
+Deno.test({
+ name: "[jwt] create",
+ fn: async function () {
+ assertEquals(
+ await create("", key),
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9..B0lmJDC8zSfMJstPqLdOAWfM265-5Svj0XrACZm8DKa1y6VJA0W7d0VoGGKJo0quKxWUdf1B1ueElNk2Yl_cLw",
+ );
+ assertEquals(
+ await create({}, key),
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.e30.dGumW8J3t2BlAwqqoisyWDC6ov2hRtjTAFHzd-Tlr4DUScaHG4OYqTHXLHEzd3hU5wy5xs87vRov6QzZnj410g",
+ );
+ assertEquals(
+ await create({ foo: "bar" }, key),
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.WePl7achkd0oGNB8XRF_LJwxlyiPZqpdNgdKpDboAjSTsWq-aOGNynTp8TOv8KjonFym8vwFwppXOLoLXbkIaQ",
+ );
+ assertEquals(
+ await create("null", key),
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.bnVsbA.tv7DbhvALc5Eq2sC61Y9IZlG2G15hvJoug9UO6iwmE_UZOLva8EC-9PURg7IIj6f-F9jFWix8vCn9WaAMHR1AA",
+ );
+ assertEquals(
+ await create("[]", key),
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.W10.BqmZ-tVI9a-HDx6PpMiBdMq6lzcaqO9sW6pImw-NRajCCmRrVi6IgMhEw7lvOG6sxhteceVMl8_xFRGverJJWw",
+ );
+ },
+});
+
+Deno.test({
+ name: "[jwt] verify",
+ fn: async function () {
+ assertEquals(
+ await verify(await create("", key, { header: header }), key, {
+ algorithm: "HS256",
+ }),
+ "",
+ );
+ assertEquals(
+ await verify(
+ await create("abc", key, { header: header }),
+ key,
+ {
+ algorithm: "HS256",
+ },
+ ),
+ "abc",
+ );
+
+ await assertEquals(
+ await verify(await create("null", key), key),
+ null,
+ );
+
+ await assertEquals(
+ await verify(await create("true", key), key),
+ true,
+ );
+
+ assertEquals(
+ await verify(
+ await create(payload, key, { header: header }),
+ key,
+ {
+ algorithm: "HS256",
+ },
+ ),
+ payload,
+ );
+ await assertEquals(
+ await verify(await create({}, key), key),
+ {},
+ );
+ await assertEquals(
+ await verify(await create("[]", key), key),
+ [],
+ );
+ await assertEquals(
+ await verify(await create(`["a", 1, true]`, key), key),
+ ["a", 1, true],
+ );
+
+ await assertThrowsAsync(
+ async () => {
+ // payload = { "exp": false }
+ await verify(
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOmZhbHNlfQ.LXb8M9J6ar14CTq7shnqDMWmSsoH_zyIHiD44Rqd6uI",
+ key,
+ );
+ },
+ Error,
+ "The token is invalid.",
+ );
+
+ await assertThrowsAsync(
+ async () => {
+ await verify("", key);
+ },
+ Error,
+ "The serialization is invalid.",
+ );
+
+ await assertThrowsAsync(
+ async () => {
+ await verify("invalid", key);
+ },
+ Error,
+ "The serialization is invalid.",
+ );
+
+ await assertThrowsAsync(
+ async () => {
+ await verify(
+ await create({
+ // @ts-ignore */
+ exp: "invalid",
+ }, key),
+ key,
+ );
+ },
+ Error,
+ "The token is invalid.",
+ );
+ },
+});
+
+Deno.test({
+ name: "[jwt] decode",
+ fn: async function () {
+ assertEquals(
+ decode(
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.TVCeFl1nnZWUMQkAQKuSo_I97YeIZAS8T1gOkErT7F8",
+ ),
+ {
+ header: { alg: "HS256", typ: "JWT" },
+ payload: {},
+ signature:
+ "4d509e165d679d959431090040ab92a3f23ded87886404bc4f580e904ad3ec5f",
+ },
+ );
+ assertThrows(
+ () => {
+ decode("aaa");
+ },
+ TypeError,
+ "The serialization is invalid.",
+ );
+
+ assertThrows(
+ () => {
+ decode("a");
+ },
+ TypeError,
+ "Illegal base64url string!",
+ );
+
+ assertThrows(
+ () => {
+ // "ImEi" === base64url("a")
+ decode("ImEi.ImEi.ImEi.ImEi");
+ },
+ TypeError,
+ "The serialization is invalid.",
+ );
+
+ const jwt =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
+ const header: Header = {
+ alg: "HS256",
+ typ: "JWT",
+ };
+ const payload = {
+ sub: "1234567890",
+ name: "John Doe",
+ iat: 1516239022,
+ };
+ assertEquals(decode(jwt), {
+ header,
+ payload,
+ signature:
+ "49f94ac7044948c78a285d904f87f0a4c7897f7e8f3a4eb2255fda750b2cc397",
+ });
+ assertEquals(await create(payload, "your-256-bit-secret", { header }), jwt);
+ },
+});
+
+Deno.test({
+ name: "[jwt] expired token",
+ fn: async function () {
+ const payload = {
+ iss: "joe",
+ jti: "123456789abc",
+ exp: 20000,
+ };
+ const header: Header = {
+ alg: "HS256",
+ dummy: 100,
+ };
+
+ await assertThrowsAsync(
+ async () => {
+ await verify(await create({ exp: 0 }, key), key);
+ },
+ Error,
+ "The token is expired.",
+ );
+
+ await assertThrowsAsync(
+ async () => {
+ await verify(
+ await create(payload, key, { header }),
+ key,
+ { algorithm: "HS256" },
+ );
+ },
+ Error,
+ "The token is expired.",
+ );
+ },
+});
+
+Deno.test({
+ name: "[jwt] none algorithm",
+ fn: async function () {
+ const payload = {
+ iss: "joe",
+ jti: "123456789abc",
+ };
+ const header: Header = {
+ alg: "none",
+ dummy: 100,
+ };
+ const jwt = await create(payload, key, { header });
+ const validatedPayload = await verify(jwt, "keyIsIgnored", {
+ algorithm: "none",
+ });
+ assertEquals(validatedPayload, payload);
+ },
+});
+
+Deno.test({
+ name: "[jwt] HS256 algorithm",
+ fn: async function () {
+ const header: Header = {
+ alg: "HS256",
+ typ: "JWT",
+ };
+ const payload = {
+ sub: "1234567890",
+ name: "John Doe",
+ iat: 1516239022,
+ };
+ const jwt = await create(payload, key, { header });
+ const validatedPayload = await verify(jwt, key, { algorithm: "HS256" });
+ assertEquals(
+ jwt,
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o",
+ );
+ assertEquals(validatedPayload, payload);
+ assertThrowsAsync(
+ async () => {
+ const invalidJwt = // jwt with not supported crypto algorithm in alg header:
+ "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.bQTnz6AuMJvmXXQsVPrxeQNvzDkimo7VNXxHeSBfClLufmCVZRUuyTwJF311JHuh";
+ await verify(invalidJwt, "", {
+ algorithm: "HS256",
+ });
+ },
+ Error,
+ `The token's algorithm does not match the specified algorithm 'HS256'.`,
+ );
+ },
+});
+
+Deno.test({
+ name: "[jwt] HS512 algorithm",
+ fn: async function () {
+ const header: Header = { alg: "HS512", typ: "JWT" };
+ const payload = {
+ sub: "1234567890",
+ name: "John Doe",
+ admin: true,
+ iat: 1516239022,
+ };
+ const jwt = await create(payload, key, { header });
+ const validatedPayload = await verify(jwt, key, { algorithm: "HS512" });
+ assertEquals(validatedPayload, payload);
+ },
+});