Home > Enterprise >  Verifying JWT tokens generated with PHP in Node
Verifying JWT tokens generated with PHP in Node

Time:01-03

I'm working on implementing JWT verification in a client-side web-application that's using Webpack5. When a user is created on the backend running PHP, I create a public and private keypair for use in JWT like this and store them:

$keyPair = sodium_crypto_sign_keypair();
$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));
$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));

Then with firebase/php-jwt create the request like this:

$jwt = JWT::encode($payload, $privateKey, 'EdDSA');

However, using jose I get the following error when attempting to verify the JWT:

import * as jose from 'jose';
const { payload, protectedHeader } = await jose.jwtVerify(response.token, window.atob(response.key));
console.log(payload);
console.log(protectedHeader);

TypeError: Key must be of type CryptoKey.

I'm not understanding how it wants the key as both the base64 string and base64 presentation of the publicKey is met with the same TypeError in node. What am I doing wrong here?

CodePudding user response:

Edwards-Curve Digital Signature Algorithm (EdDSA in JOSE) is not supported by WebCryptography API, as such you won't succeed with any library that uses native browser crypto. Sadly.

There's a proposal for Ed25519, Ed448, X25519, and X448 to be added in WebCrypto but it'll take a while before adoption and then implementation. When that happens jose will support this action. That being said you'll need to pass a CryptoKey. That's either imported via crypto.subtle.importKey directly, or via jose's import key functions. SubtleCrypto supports raw public key imports, jose supports PEM key imports in its wrappers only.

See Browser Support for the browser algorithm support matrix, as well as Algorithm Key Requirements for algorithm key requirements, and Type alias: KeyLike for how to obtain the right key representation.

As for the other suggestions above, they do not take into account the algorithm you use, suggesting to use Uint8Array instead of a CryptoKey is wrong, Uint8Array use is exclusive for symmetric algorithms. Likewise, using transpiled jsonwebtoken is only remotely reliable for HMAC based algorithms.

jose only uses the runtime's native crypto layer which is your best bet at conformance and secure implementation but when running cross-runtimes you need the consider the intersection of supported algorithms on the platform.

CodePudding user response:

It appears you are using the jose package, not node-jose as indicated in your question.

Anyways, in the documentation for jwtVerify, we see the type definition for key:

KeyLike | Uint8Array

For your context, this means you eitherwant a CryptoKey or a Uint8Array instead of an Ascii string like what window.atob(response.key) returns. You should consider using a CryptoKey via CryptoKey.import, but passing in a Uint8Array will work too (and you will need it for CryptoKey.import anyways).

Try something like this:

var encoder = new TextEncoder('utf-8');
const { payload, protectedHeader } = await jose.jwtVerify(response.token, encoder.encode(window.atob(response.key)));

PS: I recommend the jsonwebtoken package for client-side JWT validation over jose. https://www.npmjs.com/package/jsonwebtoken.

  • Related