sjudson / paseto.js

PASETO: Platform-Agnostic Security Tokens
MIT License
269 stars 16 forks source link

Add `decode` example to your README, perhaps? #5

Closed mceachen closed 5 years ago

mceachen commented 5 years ago

@sjudson, thanks for writing this library!

A couple thoughts from a new user of your library:

  1. You may want to remove the example code that doesn't set up secret key material.
  2. I was surprised that the .inject method returned undefined (and was a setter method). From the examples it looked like you were using a fluent API, but I missed the fact that your promise callback ignored the result from the promise. (I know, PEBCAK...)
  3. I didn't see a .decode example in the README.

Here's the little test script I wrote, feel free to include it in your README.

const Paseto = require("paseto.js");
const assert = require("assert");

(async () => {
  try {
    // This example uses symmetric key encryption, where the same private key is
    // used to both encrypt and decrypt.
    const sk = new Paseto.SymmetricKey(new Paseto.V2());

    // Secret keys must be at least 256 bits. 
    // Key material longer than 256 bits is ignored.
    const secretKey = Buffer.from(
      "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
      "hex"
    );

    // Provide your secret key material to SymmetricKey.
    // Note that `.inject` returns a Promise that you must `await` before continuing.
    await sk.inject(secretKey);

    // If you don't provide your private key before encoding, you'll get a runtime
    // error.

    const encoder = sk.protocol();

    // Payloads are either strings or Buffers. We're `JSON.stringify`ing our
    // payload object here just as an example:
    const payload = JSON.stringify({ payload: 123 });

    const encryptedToken = await encoder.encrypt(payload, sk);

    // "v2.local.khvgHKw7YcOsVwJ01epdAgBdB2gmrgrRQb8EbCsh7JBKpJqE5-Mp3-kRgWzfcRWRi1KvHkjDRA"
    console.dir({ encryptedToken });

    // Example decryption:
    const decryptedPayload = await encoder.decrypt(encryptedToken, sk);

    // Validate that the payload survived paseto-ization:
    assert(payload == decryptedPayload);

  } catch (err) {
    console.error("caught", err);
  }
})();
sjudson commented 5 years ago

You may want to remove the example code that doesn't set up secret key material.

Which example? All my examples either inject secret key material or call a key generation method (except for the promise vs. callback vs. async/await example at the end, but that's not trying to show a full flow).

I was surprised that the .inject method returned undefined (and was a setter method). From the examples it looked like you were using a fluent API, but I missed the fact that your promise callback ignored the result from the promise. (I know, PEBCAK...)

The entirety of the core API is async primarily because libsodium loads async, and so there are methods that you might expect to be sync that can't be due to that. As such, I figured making everything async actually made the API more straightforward. Some of the helper methods are sync, but an encryption/signing flow should be a callback/promise chain.

As for .inject being a setter, one of the principles of modern cryptographic API design I was trying to follow is to use an interface to cryptographic operations that is prepared before the message to be operated on is introduced. This helps prevent mistakes with configurations, encodings, and timing oracles that more classic encrypt(key: string, iv: string, message: string, options: object) APIs may indirectly lead to. Hence the general lack of fluidity in the API - you already must have the interface to e.g. load a key into it, so there's no reason for the loading to return the interface back. And it's not like Javascript is going to limit scoping in a way that prevents you from accessing the interface anywhere you need.

I didn't see a .decode example in the README.

Not sure I follow, there isn't a .decode method in the library. Do you mean how you'd write an end to end decryption/verification flow of a JSON object? That's something that'll be part of the higher level API when I find the time to work on it (aiming for over the holidays).

mceachen commented 5 years ago

Which example?

The first one: https://github.com/sjudson/paseto.js/blame/master/README.md#L33

The entirety of the core API is async

Sure, and I think that's reasonable and defensible.

there isn't a .decode method in the library

Sorry, I mis-typed in the doc (but not the example)--I meant .decrypt.

Do you mean how you'd write an end to end decryption/verification flow of a JSON object

Exactly.

That's something that'll be part of the higher level API

Fair enough. Again, thanks for writing the library. I'll close this now.

sjudson commented 5 years ago

The .symmetric call generates a key in that example.

mceachen commented 5 years ago

The .symmetric call generates a key in that example

Yeah, that's what I understood. What is the use case for that, though? A single-process server sending very ephemeral tokens?

sjudson commented 5 years ago

Usually you'd persist the key for future use by extracting the raw key from the returned key object. The goal is that paseto will generate the right amount of bytes for you the right way, so you don't have to figure that out yourself. It's true you wouldn't likely generate it and then immediately construct a token without that persistence first, but the example is just to give a minimal example of what the library does.