Coder Social home page Coder Social logo

tsndr / cloudflare-worker-jwt Goto Github PK

View Code? Open in Web Editor NEW
572.0 7.0 47.0 368 KB

A lightweight JWT implementation with ZERO dependencies for Cloudflare Workers.

License: MIT License

TypeScript 100.00%
jwt jsonwebtoken cloudflare-worker cloudflare-workers npm npm-package zero-dependency zero-dependencies lightweight esm

cloudflare-worker-jwt's Introduction

Cloudflare Worker JWT

A lightweight JWT implementation with ZERO dependencies for Cloudflare Workers.

Contents

Install

npm i @tsndr/cloudflare-worker-jwt

Examples

Basic Example

async () => {
    import jwt from '@tsndr/cloudflare-worker-jwt'

    // Creating a token
    const token = await jwt.sign({ name: 'John Doe', email: '[email protected]' }, 'secret')

    // Verifing token
    const isValid = await jwt.verify(token, 'secret')

    // Check for validity
    if (!isValid)
        return

    // Decoding token
    const { payload } = jwt.decode(token)
}

Restrict Timeframe

async () => {
    import jwt from '@tsndr/cloudflare-worker-jwt'

    // Creating a token
    const token = await jwt.sign({
        name: 'John Doe',
        email: '[email protected]',
        nbf: Math.floor(Date.now() / 1000) + (60 * 60),      // Not before: Now + 1h
        exp: Math.floor(Date.now() / 1000) + (2 * (60 * 60)) // Expires: Now + 2h
    }, 'secret')

    // Verifing token
    const isValid = await jwt.verify(token, 'secret') // false

    // Check for validity
    if (!isValid)
        return

    // Decoding token
    const { payload } = jwt.decode(token) // { name: 'John Doe', email: '[email protected]', ... }
}

Usage


Sign

jwt.sign(payload, secret, [options])

Signs a payload and returns the token.

Arguments

Argument Type Status Default Description
payload object required - The payload object. To use nbf (Not Before) and/or exp (Expiration Time) add nbf and/or exp to the payload.
secret string required - A string which is used to sign the payload.
options string, object optional HS256 Either the algorithm string or an object.
options.algorithm string optional HS256 See Available Algorithms
options.keyid string optional undefined The keyid or kid to be set in the header of the resulting JWT.

return

Returns token as a string.


Verify

jwt.verify(token, secret, [options])

Verifies the integrity of the token and returns a boolean value.

Argument Type Status Default Description
token string required - The token string generated by jwt.sign().
secret string required - The string which was used to sign the payload.
options string, object optional HS256 Either the algorithm string or an object.
options.algorithm string optional HS256 See Available Algorithms
options.clockTolerance number optional 0 Clock tolerance in seconds, to help with slighly out of sync systems.
options.throwError boolean optional false By default this we will only throw implementation errors, only set this to true if you want verification errors to be thrown as well.

throws

If options.throwError is true and the token is invalid, an error will be thrown.

return

Returns true if signature, nbf (if set) and exp (if set) are valid, otherwise returns false.


Decode

jwt.decode(token)

Returns the payload without verifying the integrity of the token. Please use jwt.verify() first to keep your application secure!

Argument Type Status Default Description
token string required - The token string generated by jwt.sign().

return

Returns an object containing header and payload:

{
    header: {
        alg: 'HS256',
        typ: 'JWT'
    },
    payload: {
        name: 'John Doe',
        email: '[email protected]'
    }
}

Available Algorithms

  • ES256
  • ES384
  • ES512
  • HS256
  • HS384
  • HS512
  • RS256
  • RS384
  • RS512

cloudflare-worker-jwt's People

Contributors

alaister avatar badoge avatar canrau avatar dlackty avatar imzihad21 avatar kira924age avatar ktibow avatar le0developer avatar michaelcereda avatar nickthegroot avatar plesiv avatar tomlokhorst avatar tsndr avatar workeffortwaste avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

cloudflare-worker-jwt's Issues

Cannot find module ... @tsndr/cloudflare-worker-jwt/utils

FYI, builds on Cloudflare Pages started failing in the last few days:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/opt/buildhome/repo/node_modules/@tsndr/cloudflare-worker-jwt/utils' imported from /opt/buildhome/repo/node_modules/@tsndr/cloudflare-worker-jwt/index.js

My package.json was: @tsndr/cloudflare-worker-jwt": "^2.2.1. I removed the ^ and builds are working again. I also tried 2.4.3 but saw the same error when running locally.

In case it matters, I use the module format to import the library:

import jwt from '@tsndr/cloudflare-worker-jwt'

Deployment Log:

  • Success 10:40AM January 20, 2024 (^2.2.1)
  • Failed 5:42PM January 25, 2024 (^2.2.1)
  • Success 1:46PM January 27, 2024 (2.2.1)

Error on Node 16.17.0

I'm trying to access this function on Node 16.17.0 jwt.sign({ ...payload }, secret);

Screenshot 2022-08-27 at 12 19 56 AM

Can't use @ in payload

I found out the hard way that you can't use the @ character in the payload.
The sign function will generate a valid token, but the decode function will return null and the valid function will be false (or throw PARSE_ERROR).
Using encodeURIComponent or %40 instead will fix it, but it doesn't seem that one should have to encode every payload before passing it to sign.

edit: Using secret string, e.g. 423e72ec59e3735d6e0cd5ed16707ba87659ff9aa0b938228fbeda5594c82867.

Can't verify external tokens

jwt.verify always returns false when I try to verify tokens I receive from Twitch.
Also when I try to send a request to the Twitch API it returns an error saying it could not verify the token generated by jwt.sign

I think Twitch is expecting a token signed with a base64 encoded secret but when I decode the tokens generated by jwt.sign it doesn't seem be using a base64 encoded secret.

verify function fail

hello, There may be a problem here, it cause verify fail

if (payload.nbf && Math.abs(payload.nbf - now) > (options.clockTolerance ?? 0))
throw new Error('NOT_YET_VALID')
if (payload.exp && Math.abs(payload.exp - now) > (options.clockTolerance ?? 0))
throw new Error('EXPIRED')

I think Math.abs should not be used. Math.abs(...) > 0 almost.

Verify always returns false.

Hi, verify returns false, always. It was working in the middleware files but now it's not working. Note I update my node js to the latest version, and now it's not working. However it was working when I had node v18. Not sure if it's related.

Here'e my code:
const jwt_token = await jwt.sign(
{ ...filteredData, expires: expires },
process.env.AUTH_SECRET
);

const verifyRes = await jwt.verify(jwt_token, process.env.AUTH_SECRET); // returns false here

How to got keyPair same as in test case?

Hello @tsndr

I try to use fast-jwt sign and use cloudflare worker verify but i don't know how to get keyPair?

The openssl keyPair can work with fast-jwt well, but this can't work with cloudflare-worker-jwt.

openssl ecparam -genkey -name prime256v1 -noout -out private.pem
openssl ec -in private.pem -pubout -out public.pem
import { createSigner, createVerifier } from "fast-jwt";
import jwt from "@tsndr/cloudflare-worker-jwt";

const payload = {
  name: "John Doe",
  email: "[email protected]",
  exp: Math.floor(Date.now() / 1000) + 120, // Expires: Now + 2min
};

const publicKey = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWMhgL0sObd5O80EQDRtqg14QEAzQ
dLrv/0TZhsi89R0h8pneZSGkC7WkvcJCo6dFcSDCpeMIT0C8tUQw8EapzA==
-----END PUBLIC KEY-----`;

const privateKey = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIHTfR9TWqYLBVOHadeB3uJm13PcXCfpoWeERXtJfIpG/oAoGCCqGSM49
AwEHoUQDQgAEWMhgL0sObd5O80EQDRtqg14QEAzQdLrv/0TZhsi89R0h8pneZSGk
C7WkvcJCo6dFcSDCpeMIT0C8tUQw8EapzA==
-----END EC PRIVATE KEY-----`;

const signSync = createSigner({
  algorithm: "ES256",
  key: privateKey,
});

const token = signSync(payload);

console.log(token);

const verifySync = createVerifier({ key: publicKey });
const res = verifySync(token);
console.log(res);

const algorithm = "ES256";

const jwttoken = await jwt.sign(payload, privateKey, { algorithm });

console.log(jwttoken, await jwt.verify(jwttoken, publicKey, { algorithm }));

But your test case keyPair can works with both.

if (algorithm === 'ES256') {
secrets[algorithm] = {
public: `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----`,
private: `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----`
}
} else if (algorithm === 'ES384') {

I try to use SubtleCrypto but can't works.

Node.js v19.0.0

function formatPem(str) {
  let finalString = "";

  while (str.length > 0) {
    finalString += str.substring(0, 64) + "\n";
    str = str.substring(64);
  }

  return finalString;
}

export function encodeBase64(str) {
  return typeof window === "undefined"
    ? Buffer.from(str, "utf8").toString("base64")
    : window.btoa(str);
}

function ab2str(buf) {
  return String.fromCharCode.apply(null, new Uint8Array(buf));
}

async function exportPublicKey(key) {
  const exported = await crypto.subtle.exportKey("spki", key.publicKey);
  const exportedAsString = ab2str(exported);
  const exportedAsBase64 = formatPem(encodeBase64(exportedAsString));
  const pemExported = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}-----END PUBLIC KEY-----`;

  return pemExported;
}

async function exportPrivateKey(key) {
  const exported = await crypto.subtle.exportKey("pkcs8", key.privateKey);
  const exportedAsString = ab2str(exported);
  const exportedAsBase64 = formatPem(encodeBase64(exportedAsString));
  const pemExported = `-----BEGIN PRIVATE KEY-----\n${exportedAsBase64}-----END PRIVATE KEY-----`;

  return pemExported;
}

const keypair = await crypto.subtle.generateKey(
  {
    name: "ECDSA",
    namedCurve: "P-256",
    publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
  },
  true,
  ["sign", "verify"]
);

console.log(await exportPublicKey(keypair));

console.log(await exportPrivateKey(keypair));

Node support?

Is it possible to add Node support? I have tests for workers code that run in node environment but I get this: throw new Error('SubtleCrypto not supported!')

I also saw this issue and it seems like the same problem. However, this doesn't help:

import { webcrypto } from 'node:crypto'
Object.defineProperty(globalThis, 'crypto', {
  value: { subtle: webcrypto.subtle },
})

HMAC "jwk" key import requires a JSON Web Key with Key Type parameter ("kty") equal to "oct" (encountered "RSA")

I started getting these today. My tokens are from auth0, I checked that the token is ok on https://jwt.io/ and that it is verified there with the same key.

Stack trace from sentry:

DataError: HMAC "jwk" key import requires a JSON Web Key with Key Type parameter ("kty") equal to "oct" (encountered "RSA").
  at importJwk(index.js:15470:30)
  at importKey(index.js:15480:12)
  at Object.verify(index.js:15540:23)

Looks like this is a cloudflare problem and the error is returned from crypto.subtle.importKey("jwk"

Type definition does not allow for `kid`

In this case, trying to sign a JWT:

error TS2353: Object literal may only specify known properties, and 'kid' does not exist in type '{ typ?: string | undefined; }'.

     {algorithm: 'RS256', header: {kid: REDACTED, typ: 'JWT'}},

Maybe an error

Hi,

sorry to poke you with this, but I'm at a dead end:

I have a CF worker that needs a JWT to access a Ghost site (v5.15) admin-api. For a week now I have been trying to make that work, but regardless of what I try, I cannot get it working.

I have this issue over at the ghost forum: https://forum.ghost.org/t/cloudflare-worker-jwt/37729 with a copy of the worker script that might shed some light on this.

I have a node-script using jsonwebtoken with same setup to double-check verification & debugging, and that succeeds.

After numerous videos and online tutorials on JWT, this is my list of possible reasons:

  1. My poor understanding: I just need to encode the secret some way I haven't tried
  2. The Ghost version is borked, but I can't find anything indicating this
  3. There is a difference in the encoding of the secret on it's way to hashing between jsonwebtoken and this repo

-Any pointer on where to look?

Thanks!

Changes to NodeJS ESM Syntax

Hi Toby,

The latest release v2.2.9 has issue with NodeJS release v21.1. The issue did not exist with release v2.2.5


/home/xxx/node_modules/@tsndr/cloudflare-worker-jwt/index.js:94
export async function verify(token, secret, options = { algorithm: 'HS256', skipValidation: false, throwError: false }) {
^^^^^^

SyntaxError: Unexpected token 'export'


I tried to do some digging, it looks like NodeJS has recently made some changes in their ESM Syntax
Current Workaround to solve this problem: $ node --experimental-detect-module app.js

Here is the release note of NodeJS by @targos @GeoffreyBooth
https://github.com/nodejs/node/releases/tag/v21.1.0

Thanks
Richard

Expired or wrong signature?

I need to know exactly why a token failed verification.

Using the jwt.verify function I don't know the reason as it might be expired or wrongly signed.

So I have implemented the following workaround:

let validationMessage = "ok";
let isValid = false;
try {
  isValid = await jwt.verify(token, SECRET);
  if (!isValid) {
    if (jwtClaims.exp < Math.floor(Date.now() / 1000)) {
      throw new Error('token expired')
    }
    throw new Error('wrong signature')
  }
} catch (error) {
  validationMessage = `jwt verify failed: ${error.message}`;
}

I would like to suggest throwing such errors from jwt.verify and only returning true in case of success (never return false).

I could prepare PR but it's a breaking change, so I'm not sure if you accept it.

Maybe as a separate verify_unsafe function or add a boolean thow=false flag to the existing func?

What do you think?

method jwt.sign() may have a bit of problem

hi,When I use this library's JWT.SIGN method to sign for Apple login, Apple's interface always returns {"error": "invalid_client"}, and when I switch to the jwt.sign method of JSONWEBTOKEN, Apple's interface can be correct return

Verify Token Failing

Hey, below is the function am using for issuing a token and later when I try to verify the token immediately, I get false.

I tried HS256, HS512 algorithms, same issue. surprisingly, verify works if I don't pass nbf and exp whilst issuing.

import jwt from "@tsndr/cloudflare-worker-jwt"

const issueToken = async (payload: any, secret: string) => {
    return await jwt.sign({
        ...payload,
        nbf: Math.floor(Date.now() / 1000) + (60 * 60), // Not before: Now + 1h
        exp: Math.floor(Date.now() / 1000) + (2 * (60 * 60)) // Expires: Now + 2h
    }, secret)
}

Thank you again for this library :)

Verifying with Public Key of RSA key triggers error

I am unable to verify the JWT generated with the private RSA key using the public key.
But verifying with the private key works. Which leads me to think that the implementation does not factor the public key usage.

The expected behaviour is that I should be able to verify the token with the public key.

Steps to reproduce this.

  1. Generate the private key
    openssl genpkey -algorithm RSA -out ./credentials/jwt-rsa-key -pkeyopt rsa_keygen_bits:4096

  2. Generate the public key
    openssl rsa -pubout -in ./credentials/jwt-rsa-key -out ./credentials/jwt-rsa-key.pub

  3. Code
    const jwt = require('@tsndr/cloudflare-worker-jwt');

let rsaPrivate = "-----BEGIN......."; // private key
let rsaPublic = "-----BEGIN......"; // public key

let credential = {
name: "Someone",
email: "[email protected]",
nbf: Math.floor(Date.now() /1000) - 60,
exp: Math.floor(Date.now() / 1000) + (10 * 60) // 10 mins expiry
}

const token = await jwt.sign(credential, rsaPrivate, { algorithm: 'RS256' });
const isValid = await jwt.verify(token, rsaPublic, { algorithm: 'RS256' }); <------ fails here
const decoded = jwt.decode(token);

The jwt.verify fails when rsaPublic is used. But it passes when rsaPrivate is used.

Couple feature requests

First off thank you so much for this library. You just single handedly shaved nearly 1MB off my build now that I can toss jsonwebtoken.

  1. Option for outputting the payload during verify
try {
  await JWT.verify(authToken, JWT_TOKEN)
  jwt = await JWT.decode(authToken)
} catch(err) {
  throw {status: 401, message: `Invalid or expired authorization token`}
}

INTO

try {
  jwt = await JWT.verify(authToken, JWT_TOKEN)
} catch(err) {
  throw {status: 401, message: `Invalid or expired authorization token`}
}
  1. Validate exp keys during verify

  2. Support for Buffer secrets

JWT.verify(authToken, Buffer.from(JWT_TOKEN, 'base64'))
  1. Support for es6 import destructuring syntax
import { verify } from '@tsndr/cloudflare-worker-jwt'
  1. If you're feeling really ambitions support verifying complex JWSs like what is being sent back from the Android safetynet attestation. https://developer.android.com/training/safetynet/attestation ๐Ÿ˜ฌ

JwtPayload is too lax

So basically, JwtPayload<T> is pretty lax because of the [key: string]: any that is always included (see line 62):

export type JwtPayload<T = {}> = {
/** Issuer */
iss?: string
/** Subject */
sub?: string
/** Audience */
aud?: string | string[]
/** Expiration Time */
exp?: number
/** Not Before */
nbf?: number
/** Issued At */
iat?: number
/** JWT ID */
jti?: string
[key: string]: any
} & T

Currently the following will work just fine:

import type { JwtPayload } from "@tsndr/cloudflare-worker-jwt";

type StrictPayload = JwtPayload<{
  something: string;
}>

const payload = { ... } satisfies StrictPayload;
console.log(payload.this_does_not_exist); // fine because [key: string]: any

Instead, I'd like the [key: string]: any to be part of the default T, so that we can get strict type-checking for missing fields.

Proposed change:

- export type JwtPayload<T = {}> = {
+ export type JwtPayload<T = {[key: string]: any}> = {
  // ...
- [key: string]: any
} & T

The default behavior will remain the same but custom types will enjoy stricter type-checking.

Signing/Verifying an ES256 JWT fails

Using jwt.sign to create a JWT to post to Apple's Device Check API (docs) is resulting in an error "Missing or badly formatted authorization token". Apple's API requires an ES256 algorithm, private key and kid in the header.

I think there may a problem when signing with this algorithm so I wrote a unit test to sign and then verify the signed token which fails with an error InvalidAccessError: Unable to use this key to verify.

Here's the unit test to use:

import { describe, expect, test } from '@jest/globals'
import jwt from '../src/index'

describe('Test ES256 Sign and Verify', () => {
    test('ES256 Sign and Verify', async () => {        
        const secret = '-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2\nOF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r\n1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G\n-----END PRIVATE KEY-----';
        const token = await jwt.sign({
            iss: '123456789',
            iat: Math.floor(Date.now() / 1000) - (1 * 360),
            exp: Math.floor(Date.now() / 1000) + (24 * (60 * 60))
        }, secret, { algorithm: 'ES256', header: { kid: '987654321', type: 'JWT' } });

        const verified = await jwt.verify(token, secret, { algorithm: 'ES256', throwError: true })
        expect(verified).toBeTruthy()

    })
})

Perhaps I've written a bad unit test or Apple just doesn't like my token, but I figured it would be worth posting here to see if it looks like a real issue as I would think calling sign to get a token and then verifying it would result in a verified token.

proper way to sign with header

Quick question, if i needed to sign something with the following header:

{
  "alg": "HS256",
  "typ": "JWT",
  "dd-ver": "AA-JWT-V1"
}

What would be the proper way to go about doing this with your package...i seem to be failing.

Thanks!

Unable to use RSA 512 key pair

Hey so I've a simple nextjs app where I want to sign/verify jwt in the middleware as well as during request phase. I am trying to use RS512 to sign/verify tokens but it seems like it's not supported in node.js ? Can you help me debug this? Thanks!,

DOMException [NotSupportedError]: Unable to import RSA key with format raw
    at new DOMException (node:internal/per_context/domexception:53:5)
    at __node_internal_ (node:internal/util:663:10)
    at Object.rsaImportKey (node:internal/crypto/rsa:287:13)
    at SubtleCrypto.importKey (node:internal/crypto/webcrypto:616:10)
    at Object.sign (webpack-internal:///(action-browser)/./node_modules/.pnpm/@[email protected]/node_modules/@tsndr/cloudflare-worker-jwt/index.js:154:37)
    at Object.create (webpack-internal:///(action-browser)/./app/lib/auth/session.ts:55:94)
    at login (webpack-internal:///(action-browser)/./app/(auth)/login/actions.ts:27:60)

Can you help me debug this?

Here's what my session file looks like,

/**
 * Creates a new session token with provided information
 *
 * @example
 * ```js
 * Session.create({
 *   type: "user",
 *   properties: {
 *     userID: "123"
 *   }
 * })
 * ```
 */
async function create<T extends keyof SessionTypes>(input: {
  type: T
  properties: SessionTypes[T]
  options?: Partial<JwtPayload>
}) {
  const secret = env.AUTH_PRIVATE_KEY
  const token = await jwt.sign(
    {
      ...input.options,
      type: input.type,
      properties: input.properties,
    },
    secret,
    { algorithm: "RS512" },
  )
  cookies().set("auth_token", token)
  return token as string
}

/**
 * Verifies a session token and returns the session data
 *
 * @example
 * ```js
 * Session.verify()
 * ```
 */
async function verify<T = SessionValue>(token: string) {
  if (token) {
    try {
      const isValid = await jwt.verify(token, env.AUTH_PUBLIC_KEY, {
        algorithm: "RS512",
      })
      if (!isValid) throw new Error("Invalid token")
      const { payload } = jwt.decode(token)
      return payload as T
    } catch (e) {}
  }
  return {
    type: "public",
    properties: {},
  }
}

jwt.verify() always evaluates as false for Google-issued JWT when using certificate verification

Hi -

Using @tsndr/[email protected] I always get false when trying to verify a Google-issued JWT with a certificate.

This is basically the same issue as #28 logged by @Zombobot1

I'm using JWTs and certificates issued by Google firebase. The certificates are provided here:

https://www.googleapis.com/robot/v1/metadata/x509/[email protected]

I'm extracting the kid from the JWT and using that to match with the specific certificate that should be used to validate the JWT.

I tried using the raw certificate as supplied by Google, tried removing the -----BEGIN CERTIFICATE----- prefix/suffix and tried removing line feeds but it always verifies as false.

Rather than supplying my own JWT/certificate I've re-used those from the previously raised ticket.

Would really appreciate some help with this one!

async function test() {
  const token = `eyJhbGciOiJSUzI1NiIsImtpZCI6ImE5NmFkY2U5OTk5YmJmNWNkMzBmMjlmNDljZDM3ZjRjNWU2NDI3NDAiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiQWxpIE9tYXJvdiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQUZkWnVjcVdlUWd0RnE1cVZBcDJyOC1pbU8yeVhmVXUySWhBZktGWU5PZVA9czk2LWMiLCJpbml0ViI6MSwicm9sZSI6ImFkbWluIiwiYWNjb3VudCI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3VuaXZlcnNlLTU1Y2VjIiwiYXVkIjoidW5pdmVyc2UtNTVjZWMiLCJhdXRoX3RpbWUiOjE2NjgwNDM3MTIsInVzZXJfaWQiOiJZWkZQN25RT28xUnNFakJqSjRzZHlzdWFQU28yIiwic3ViIjoiWVpGUDduUU9vMVJzRWpCako0c2R5c3VhUFNvMiIsImlhdCI6MTY2OTc0MDE4NywiZXhwIjoxNjY5NzQzNzg3LCJlbWFpbCI6ImFsaWsub21hcm92MzAwMEBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjEwNzY5NjU3NjE2NTExNjE5MDgzNyJdLCJlbWFpbCI6WyJhbGlrLm9tYXJvdjMwMDBAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiZ29vZ2xlLmNvbSJ9fQ.TJ6XeEcR3dtG-bVRDKrA4FXPN-v_U2c_HGRMWLc57Qg5lMt8sNYEhKWmFkrcoslSHPhKDRqQOD5JQlrVIgNzJoQY7pm4vSGpoTnz6pRfAtC64TrtanQaUChW8lI2StdKDNXe_j_b3F7mlZj9xFLNxjpnBim3LaF-mxLMqtC6IdQ9xENvJEsDDwOHS77VjNtY2ZhlXwnTWVq7UTbThOQETQdlYBBCLPgqMYSACzTfLli-wgfe3pIEAUXeygBxWqAgYnSDAm-Z47X1yAzQUyVlbITAfCUZZXiGonHs4YoNMWVqWDkWgNsIspKWRWUcSzgVjZFS7HChyYI_uIP0FejgsA`
  const key = `-----BEGIN CERTIFICATE-----
MIIDHTCCAgWgAwIBAgIJAJsmCdlCEtdXMA0GCSqGSIb3DQEBBQUAMDExLzAtBgNV
BAMMJnNlY3VyZXRva2VuLnN5c3RlbS5nc2VydmljZWFjY291bnQuY29tMB4XDTIy
MTExOTA5MzkwOFoXDTIyMTIwNTIxNTQwOFowMTEvMC0GA1UEAwwmc2VjdXJldG9r
ZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDPx5SngqCrMJVQ/lFC9kP7Mgnhs4aIIbaquM42Z/zG1c80
EPEhTlRz9Cltc6wtj0wmRPi9x8HtIRlyoo4ps6LCXH0GxJZ6hZHGlcGUbFTAbVsY
aUWqteiXb2umTEnFV8+IaeOqVvSnJ97RIRcMSa7McKL+AkdjKPuDvdK5R6SHnnML
3HNJ8Xla1YOWmYkgCAgUNGLLg5bl8M6zyicNg8ZPGV7ndIzjrXuy9yKorpljNzZJ
hymT29yIq3hFInk+GGaSEaRIW6Zz0QjxUSgjDS75yxHnNM9Sgik3I6X8SHKO6mdY
aG4GAQUcuHr8eaZH9vYR0RG50c+fMw/P38nr0i8VAgMBAAGjODA2MAwGA1UdEwEB
/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMA0G
CSqGSIb3DQEBBQUAA4IBAQCvRsaUOJj8yA5Ul/LhoBFwEmZaQuU5sUKWGMJJj4ug
WzBPtCEfsmYsMQWmaSY7PiHn7eOF7rUL6FRaHvy1sMwF+xp4xomrIp+GQPQA4hra
AlgJRUUslTMJkbypsZ6PMWMLJw2WtyFcaIq/5vdywExwcfi+Gi7lDHTyfSCTiDvq
qK85W7OpqkmSxFKcva9Gi0tLLNgrRR9953Pwqis3LRVwKX6yXQ0j0v2pTmIyH/zp
VrUK77PjKaWlZISsmLH5dF4Olclx6hRFhvUYVXmT0K/hqr4pBZWukt4D9cD6ZSem
qFbFBUPmxj4clHXqiqRmQ3tv6MkpudXzCNb0wYWk+3TU
-----END CERTIFICATE-----`
  console.log(await jwt.verify(token, key, { algorithm: 'RS256' }))
}

Verify function

Hi,

Line 110 of the verify function uses the signing function to verify, is this correct?

Line 110 const res = await crypto.subtle.sign(importAlgorithm, key, this._utf8ToUint8Array(tokenParts.slice(0, 2).join('.')))

Option to verify despite expiry

I'm considering a pattern where the token is very short lived, so there's an option to revoke it within a short while if needed. In this case I would want to re-issue a token if the previous one was valid but expired (and of course the user has not been banned).

In this case I want to know that it has expired, but that the signature was correct and it would be had the exp not passed. Could this be an option somehow on the verify method? "Valid in signature, though not in time" kind of thing.

Then at least I can be sure which user to go and lookup to see if they've been banned before issuing a new one. Thanks.

Doesn't work with google tokens

I'm trying to verify a token issued by google but it throws an exception. If I log exception it is empty:

DOMException {
  stack: undefined,
  code: undefined,
  name: undefined,
  message: undefined
}

Code for reproduction (I use google public key, and my token expires in 30 minutes)

async function test() {
  const token = `eyJhbGciOiJSUzI1NiIsImtpZCI6ImE5NmFkY2U5OTk5YmJmNWNkMzBmMjlmNDljZDM3ZjRjNWU2NDI3NDAiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiQWxpIE9tYXJvdiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQUZkWnVjcVdlUWd0RnE1cVZBcDJyOC1pbU8yeVhmVXUySWhBZktGWU5PZVA9czk2LWMiLCJpbml0ViI6MSwicm9sZSI6ImFkbWluIiwiYWNjb3VudCI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3VuaXZlcnNlLTU1Y2VjIiwiYXVkIjoidW5pdmVyc2UtNTVjZWMiLCJhdXRoX3RpbWUiOjE2NjgwNDM3MTIsInVzZXJfaWQiOiJZWkZQN25RT28xUnNFakJqSjRzZHlzdWFQU28yIiwic3ViIjoiWVpGUDduUU9vMVJzRWpCako0c2R5c3VhUFNvMiIsImlhdCI6MTY2OTc0MDE4NywiZXhwIjoxNjY5NzQzNzg3LCJlbWFpbCI6ImFsaWsub21hcm92MzAwMEBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjEwNzY5NjU3NjE2NTExNjE5MDgzNyJdLCJlbWFpbCI6WyJhbGlrLm9tYXJvdjMwMDBAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiZ29vZ2xlLmNvbSJ9fQ.TJ6XeEcR3dtG-bVRDKrA4FXPN-v_U2c_HGRMWLc57Qg5lMt8sNYEhKWmFkrcoslSHPhKDRqQOD5JQlrVIgNzJoQY7pm4vSGpoTnz6pRfAtC64TrtanQaUChW8lI2StdKDNXe_j_b3F7mlZj9xFLNxjpnBim3LaF-mxLMqtC6IdQ9xENvJEsDDwOHS77VjNtY2ZhlXwnTWVq7UTbThOQETQdlYBBCLPgqMYSACzTfLli-wgfe3pIEAUXeygBxWqAgYnSDAm-Z47X1yAzQUyVlbITAfCUZZXiGonHs4YoNMWVqWDkWgNsIspKWRWUcSzgVjZFS7HChyYI_uIP0FejgsA`
  const key = `-----BEGIN CERTIFICATE-----
MIIDHTCCAgWgAwIBAgIJAJsmCdlCEtdXMA0GCSqGSIb3DQEBBQUAMDExLzAtBgNV
BAMMJnNlY3VyZXRva2VuLnN5c3RlbS5nc2VydmljZWFjY291bnQuY29tMB4XDTIy
MTExOTA5MzkwOFoXDTIyMTIwNTIxNTQwOFowMTEvMC0GA1UEAwwmc2VjdXJldG9r
ZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDPx5SngqCrMJVQ/lFC9kP7Mgnhs4aIIbaquM42Z/zG1c80
EPEhTlRz9Cltc6wtj0wmRPi9x8HtIRlyoo4ps6LCXH0GxJZ6hZHGlcGUbFTAbVsY
aUWqteiXb2umTEnFV8+IaeOqVvSnJ97RIRcMSa7McKL+AkdjKPuDvdK5R6SHnnML
3HNJ8Xla1YOWmYkgCAgUNGLLg5bl8M6zyicNg8ZPGV7ndIzjrXuy9yKorpljNzZJ
hymT29yIq3hFInk+GGaSEaRIW6Zz0QjxUSgjDS75yxHnNM9Sgik3I6X8SHKO6mdY
aG4GAQUcuHr8eaZH9vYR0RG50c+fMw/P38nr0i8VAgMBAAGjODA2MAwGA1UdEwEB
/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMA0G
CSqGSIb3DQEBBQUAA4IBAQCvRsaUOJj8yA5Ul/LhoBFwEmZaQuU5sUKWGMJJj4ug
WzBPtCEfsmYsMQWmaSY7PiHn7eOF7rUL6FRaHvy1sMwF+xp4xomrIp+GQPQA4hra
AlgJRUUslTMJkbypsZ6PMWMLJw2WtyFcaIq/5vdywExwcfi+Gi7lDHTyfSCTiDvq
qK85W7OpqkmSxFKcva9Gi0tLLNgrRR9953Pwqis3LRVwKX6yXQ0j0v2pTmIyH/zp
VrUK77PjKaWlZISsmLH5dF4Olclx6hRFhvUYVXmT0K/hqr4pBZWukt4D9cD6ZSem
qFbFBUPmxj4clHXqiqRmQ3tv6MkpudXzCNb0wYWk+3TU
-----END CERTIFICATE-----`
  console.log(await jwt.verify(token, key, { algorithm: 'RS256', throwError: false }))
}

The jsonwebtoken library can verify this token

import jwt from 'jsonwebtoken'
jwt.verify(token, key, { algorithms: ['RS256'] })
console.log('token verified')

Type declarations for TypeScript is missing

TypeScript is complaining it cannot find any type declarations. I can use the declare module workaround described as one of the solutions in the picture below, but this will not provide any linting to the functions.

image

[Question / Feature request] RSA support

Hi!

JWT and OIDC gets very confusing, so excuse me if I am asking for something stupid.

I am trying to build on your solution in order to verify keys coming from a SPA, which would use RSA signed keys.
My plan is that a OIDC discovery endpoint is provided, and the keys endpoint is used to fetch the public key of the signed JWT in order to verify its authenticity.

Do you think you could add support for verifying keys with RSA from exponent and modulus?
Or point me in the right direction to how to do it, i am not too familiar with the crypto api.

adding new errrors

would it be possible to split errors:

  1. token expired
  2. token invalid.

It would be important to throw error informing that this token verification passed by it is expired.

There is no possibility to modify header

It is sometimes needed to add some parameters to the header, e.g. kid. I had to modify sign method to enable it. You should find some consistent solution as it is ugly to put another sometimes compulsory (fourth) parameter after options.

algo validation missing

hi i just noticed while looking at your code, which is very helpful for workers btw, that there is no validation if the token algo is the same as the one specified in verify() which i would expect around here.

there is a nice writeup on the auth0 site where they explain why this could be a critical security vuln.

basically it boils down to the server checking the rs256 signature while an attacker sends a hs256 signature.

i could create a pull request to fix this if you want but it more or less boils down to if (header.alg != options.alg) throw new Error('ALG_MISMATCH')

Cannot verify tokens.

Hi, thanks for the library!

I have been trying to verify tokens, and I don't know the reason why the signed tokens are always invalid, verify returns false.

I checked with the example code and verify is returning false.

I am using wrangler 2.6.1 and the 2.1.3 version of this library.

const token = await jwt.sign(
      {
        name: "John Doe",
        email: "[email protected]",
        nbf: Math.floor(Date.now() / 1000) + 60 * 60, // Not before: Now + 1h
        exp: Math.floor(Date.now() / 1000) + 2 * (60 * 60), // Expires: Now + 2h
      },
      "secret"
    );

    // Verifying token
    const isValid = await jwt.verify(token, "secret"); // false

The token generated is:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJlbWFpbCI6ImpvaG4uZG9lQGdtYWlsLmNvbSIsIm5iZiI6MTY3MDUwMjAyNSwiZXhwIjoxNjcwNTA1NjI1LCJpYXQiOjE2NzA0OTg0MjV9.-UdjUY8GJpaae2WpUTvbLsJQY8FNqVeNQiVDyNH_h0A

And verify returns a false

Crypto is not defined

Hello, I am getting a 'crypto is not defined' when building with typescript. Any ideas on what may be the issue?

[BUG] verify not working in 2.2.10

Was using version 2.2.3, everything works fine

updated to 2.2.10 and now the most basic example is not working

const token = await jwt.sign({ chroot: "chroot" }, jwtSecret);
console.log(await jwt.verify(token, jwtSecret)) // prints false

Tested locally with Node version 20.9.0 and deployed to cloudflare pages

If you need any more infos about the setup

Verifying Clerk JWT

Hello, thank you for building this library for Cloudflare Workers.

Description

I'm trying to use this library to verify the authenticity of a session token with the public key. I am using Clerk for the sessionToken and JWT, which uses RS256.

const splitPem = JWT_VERIFICATION_KEY.match(/.{1,64}/g) as string[]
const publicKey = '-----BEGIN PUBLIC KEY-----\n' + splitPem.join('\n') + '\n-----END PUBLIC KEY-----'
const authorized = await jwt.verify(sessionToken, publicKey, {
   algorithm: 'RS256'
})

jwt.verify always returns false. Any pointers?

unable to get .verify() to work

I get TypeError: Failed to execute 'importKey' on 'SubtleCrypto': 2nd argument is not instance of ArrayBuffer, Buffer, TypedArray, or DataView. error after upgrade my nodejs version from 18.12 to 18.17

const JWTService = {
  verify: async function verify(
    request: NextRequest,
  ): Promise<boolean> {
    const algorithm: JwtAlgorithm = 'ES384'
    const token = request.cookies.get('token')

    if (!token) return false

    return jwt.verify(token.value, atob(String(process.env.JWT_PUBLIC_KEY)), { algorithm })
  }
}

image

JSON Web Key (JWK) not working with RS256 algorithm

importKey uses "verify" and "sign" as keyUsages, but the RSASSA-PKCS1-v1_5 algorithm does not support signing, so trying to use verify() fails since importing the key/JWK fails.
My suggestion would be to either dynamically determine the keyUsages depending on the algorithm, or passing the keyUsages depending on the usage (e.g. a call to verify only uses verify as keyUsage).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.