forked from denoland/std
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(jwt): add a JSON Web Token library (denoland/deno#7991)
Co-authored-by: Tim Reichen <[email protected]>
- Loading branch information
1 parent
d9c8675
commit c575905
Showing
7 changed files
with
739 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); |
Oops, something went wrong.