Generate a Paseto V2 Public Key/Token in PHP, Verify in Node.js
Asked Answered
M

1

7

Preface:

What is Paseto?: https://developer.okta.com/blog/2019/10/17/a-thorough-introduction-to-paseto

  • I am using the PHP library for Paseto from here
  • I am using the Node.js Paseto lib from here

I have been able to successfully achieve the creation of a Paseto V1 token and corresponding public key using the PHP lib (with a RSA private key on the server side for keypair), and then use the public key to verify the given token on the Node.js side:

PHP Paseto Public V1:

    $privateKeyV1 = new AsymmetricSecretKey($rsaPrivate, new Version1());
    $publicKeyV1  = $privateKeyV1->getPublicKey();

    $token = (string) (new Builder())
        ->setKey($privateKeyV1)
        ->setVersion(new Version1())
        ->setPurpose(Purpose::public())
        // Set it to expire in one day
        ->setExpiration(
            (new DateTime())->add(new DateInterval('P01D'))
        )
        ->setAudience('Foo')
        ->setIssuedAt(new DateTime())
        ->setIssuer('Bar')
        ->setNotBefore()
        ->setSubject('IDP Paseto')
        ->setClaims([
            'claim' => json_decode($this->claimJSON(), true),
        ])->toString();

    return $response->withJson([
       'public_key_v1' => $publicKeyV1->raw(), 
       'token' => $token
    ]);

NodeJS Paseto Public V1:

const token    = "v1.public.sdsw5vsdf4554...............exampletoken:"; // Example paseto V1 token
const pubKey   = await createPublicKey("-----BEGIN PUBLIC KEY-----\r\npubKeyFromAbovePHP\r\n-----END PUBLIC KEY-----"); // Example public key
const response = await verify(token, pubKey);

This works great, I can verify my claim in Node.js and process what I need using the ingested data.

Now if I try the following w/ V2, calling bin2hex() on the public key to be able to store it and use it on the Node.js side, I am unable to properly verify in Node.js. I believe it has something to do with the sodium crypto binary key generation, and how the $publicKey->encode() uses Base64UrlSafe::encodeUnpadded($this->key); but am not sure.. I don't ever get the BEGIN PUBLIC KEY from the publicKey created using V2, as I believe it is just stored as binary?

PHP Paseto Public V2:

$privateKeyV2 = AsymmetricSecretKey::generate(new Version2()); 
$publicKeyV2  = $privateKeyV2->getPublicKey();

$token = (string) (new Builder())
        ->setKey($privateKeyV2)
        ->setVersion(new Version2())
        ->setPurpose(Purpose::public())
        ->setExpiration((new DateTime())->add(new DateInterval('P01D')))       
        ->setClaims([
            'claim' => json_decode($this->claimJSON(), true),
        ])->toString();

       return $response->withJson([
        'public_key_v2' => bin2hex($publicKeyV2->raw()),
        'token' => $token
       ]);

NodeJS Paseto Public V2:

const pubKey = await createPublicKey(Buffer.from('public_key_from_php_response_above', 'hex'));
const token = 'token_output_from_php_response_above';
const response = await verify(token, pubKey);
console.log(response);

I appreciate any feedback you may be able to provide. Please let me know if you have any additional questions. I tagged this with slim for the PHP framework, as I am using the PHP paseto lib within a slim project to store the public/private keys on my slim container, etc. and NodeJS within the context of a lambda.

Multilingual answered 27/1, 2020 at 22:46 Comment(2)
Have you tried using base64 instead of hex?Hopi
Hi, yes I have tried that as well. I can create a V2 Asymmetric public key w/ token in PHP, and use that same token and public key via an ingestion into a PHP service and decode okay (using the PHP paseto lib). It's just the transition from generating the token on PHP and trying to verify in node that is proving to be the issue, using V2. Thanks for commenting!Multilingual
R
1

In case anyone still needs an answer to this - using

V2.bytesToKeyObject(publicKey) 

is what worked for me.

So instead of using the crypto library's createPublicKey method, use paseto V2's bytesToKeyObject method to generate the key to feed into V2.verify.

const { V2 } = require('paseto');

const publicKeyString = Buffer.from('Wxar8cbJRI9flcB', 'base64'); // or 'hex' if that's the encoding you used initially
const pubKey = V2.bytesToKeyObject(publicKeyString);
const payload = await V2.verify(token, publicKey);

In my case, I was using a public paseto scheme, where the signer is a Ruby on Rails app, and the verifier is a downstream node app.

In Ruby, I created the public/private key pair using mguymon's paseto gem https://github.com/mguymon/paseto.rb (v2)

Rozamond answered 12/4, 2022 at 17:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.