How can I create a signed JWT using npm Jose and then verify this token?
Asked Answered
E

2

9

I am struggling to understand how to use the npm jose module (https://www.npmjs.com/package/jose) to create and verify signed JWT tokens in my Node application. My scenario is this: I want to sign an authenticated request to access a resource. I can successfully create a JWT claim for this request grant token that respects the properties of “its” and “aud”, “exp”, etc. but I want to sign it (to wit, using the SignJWT object and the ‘sign’ method) so that when it gets passed back to my server as a request I can validate it and grant or reject access. The “sign” method doesn’t seem to like anything I pass it for the ‘key’ parameter (I am not passing any options — maybe I should be, but what?). I am attempting to use RSA key pairs. I want to sign with the private key and verify with the public key. For my immediate need, I suppose I could use a symmetric key instead, but I am thinking of some other future scenarios where I will want this classic PKCS relationship of certificate keys. And at any rate, I don’t think this choice has anything to do with the current roadblock to my progress. I first tried to use jose/util/generate_key_pair to create my public/private pair. But when I went to use the key, the error informed me this was not supported by my implementation. So I switched to trying to create a ‘pem’ cert outside of my app and applying that (as text), but that also failed. The ‘sign’ method reports that the key must be a ‘KeyLike’, ‘CryptoKey’, or ‘Uint8Array’ type. Well, the UInt8Array (node buffer) is not enough type information: it doesn’t speak to what is in that buffer, and “KeyLike” is such a vague definition that it’s ignorable. After beseeching the oracles of the search engines, I found I could create a key pair in CryptoKey format using the following from Node APIs:

crypto.webcrypto.subtle.generateKey(
    {
        name: 'RSASSA-PKCS1-v1_5',
        modulusLength: 2048,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: "SHA-256"
    },
    true,
    [‘sign’, ‘verify’]
).then((pair:any) => {
    serverInstance.keyPair = pair
})

But, still, when I get to the signing part: siaToken.sign(serverInstance.keyPair.privateKey).then(signature => {

I get an exception that reports “TypeError: CryptoKey does not support this operation”

Thinking this might have to do with the ‘usages’ parameter of generateKey, I tried various values there, but with no success.

So, I am flummoxed. Can anyone tell me how to (a) best produce a pair of keys for this purpose and (b) how to apply these for JWT signing?

Execratory answered 10/9, 2021 at 1:24 Comment(0)
H
8

I have also struggled with signing and verifying JWT using jose but was finally able to succeed with HS256 symmetric key encryption. I produced it by following steps (I am using jose-node-cjs-runtime for Node.js only use case. Feel free to replace with desired package. Also please note that I have found that these codes are working for Node.js version 16.7.0, 16.9.0 so please ensure that any of them is installed. If you want to deploy these changes to production environment, then also you have to ensure the deploy environment has the same Node.js version. One way this can be achieved is by mentioning Node.js version in engines key in package.json):

Add Required imports

// library for generating symmetric key for jwt
const { createSecretKey } = require('crypto');
// library for signing jwt
const { SignJWT } = require('jose-node-cjs-runtime/jwt/sign');
// library for verifying jwt
const { jwtVerify } = require('jose-node-cjs-runtime/jwt/verify');

Create Secret key of type KeyObject

KeyObject is recommended by Node.js for using when generating symmetric, asymmetric keys. Use following code to generate and store symmetric key object of type KeyObject in secretKey.

const secretKey = createSecretKey(process.env.JWT_SECRET, 'utf-8');

Replace process.env.JWT_SECRET with a sufficiently long string. It needs to be sufficiently long (use strings of length at least 32) otherwise there will be following error thrown when signing the JWT: HS256 requires symmetric keys to be 256 bits or larger

Sign the JWT

(async () => {
  const token = await new SignJWT({ id: '12345' }) // details to  encode in the token
      .setProtectedHeader({ alg: 'HS256' }) // algorithm
      .setIssuedAt()
      .setIssuer(process.env.JWT_ISSUER) // issuer
      .setAudience(process.env.JWT_AUDIENCE) // audience
      .setExpirationTime(process.env.JWT_EXPIRATION_TIME) // token expiration time, e.g., "1 day"
      .sign(secretKey); // secretKey generated from previous step
  console.log(token); // log token to console
})();

Verify the JWT

We will use the same symmetric key stored in secretKey for verification purpose as well. Following code can be used to extract token from request header (in an Express app) and validate the token:

(async () => {
    // extract token from request
    const token = req.header('Authorization').replace('Bearer ', '');
    try {
      // verify token
      const { payload, protectedHeader } = await jwtVerify(token, secretKey, {
        issuer: process.env.JWT_ISSUER, // issuer
        audience: process.env.JWT_AUDIENCE, // audience
      });
      // log values to console
      console.log(payload);
      console.log(protectedHeader);
    } catch (e) {
      // token verification failed
      console.log("Token is invalid");
    }
})();
Hottentot answered 10/9, 2021 at 6:39 Comment(8)
Thanks. This works. It's not the asymmetrical key I was looking for originally, but like I said I don't need that functionality right now and I can try to cross that bridge if and when I ever get to it. I haven't tried to verify the token with this signature yet (I have other issues to deal with before then) but I'm confident it will work with this format since it has gotten this far so seamlessly.Execratory
followup: yes, the verify operation works as expected as well. Thanks again.Execratory
I have added the token verification step as well. Thanks for pointing this out.Hottentot
Kudos, saved me a lot of time and headache, I can also confirm it works fine. For those who are stuck with regular jose, I recommend trying node-alone version. I added back issuer, audience and issusedAt() to go through smooth verification.Sanction
Does jwtVerify return null or throw an exception on fail?Amaro
Hi @KernelJames, from jwtVerify docs (github.com/panva/jose/blob/main/docs/functions/…), it returns a Promise. So on fail expectation would be this promise is rejected.Hottentot
What values should I assign to issuer and audience?Kellby
Hi @AkshayBenny you can assign values to issuer and audience according to your requirements. You can refer to this link for understanding what these values refer to: quora.com/…Hottentot
S
1

By far the easiest way to generate the key material is to use generateKeyPair. The method is runtime agnostic and only requires a single argument - the Algorithm Identifier you wish to use the target key pair with. If you're bringing your own keys tho you must be aware of the different requirements for the key in order to be usable by the algorithm.

Not every runtime's crypto capabilities can support every algorithm, the list of available algorithms per runtime is available here.

Furthermore - importing SPKI/PKCS8 encoded key material is platform-specific and done through platform-specific APIs. The ways one can end up with KeyLike (type alias for CryptoKey (web), KeyObject (node), or Uint8Array (symmetric secrets) is documented in, well, KeyLike alias documentation linked from every function doc that uses it.

If you were to provide any actual reproduction code for your steps I would be happy to help.

The ‘sign’ method reports that the key must be a ‘KeyLike’, ‘CryptoKey’, or ‘Uint8Array’ type.

I'm fairly sure it says KeyObject at runtime, KeyLike is merely a type alias covering all the different types of inputs applicable for the different algorithms and runtimes.

Sore answered 11/9, 2021 at 13:41 Comment(1)
This is the correct answer. I consider the import of keys to be the most important feature of the asymmetric algorithm, because otherwise different keys are used in the scaling cluster instances.Corneille

© 2022 - 2024 — McMap. All rights reserved.