Skip to content

Commit

Permalink
Merge pull request #5 from carbonteq/add-validation-and-header-opts
Browse files Browse the repository at this point in the history
Add validation and header options, and asymmetric encoding/decoding
  • Loading branch information
volf52 committed Jun 26, 2023
2 parents 29613ec + c9295eb commit b32338d
Show file tree
Hide file tree
Showing 19 changed files with 806 additions and 278 deletions.
5 changes: 5 additions & 0 deletions .changeset/mean-kangaroos-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@carbonteq/jwt": minor
---

Add asymmetric key based signatures, algorithm selection and other header & validation options
5 changes: 5 additions & 0 deletions .changeset/twenty-cars-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@carbonteq/jwt": patch
---

Remove decode and signClaims, and clean up constructor
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ version = "0.0.0"
crate-type = ["cdylib"]

[dependencies]
jsonwebtoken = { version = "8.3.0", default-features = false }
jsonwebtoken = { version = "8.3.0" }
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.12.2", default-features = false, features = [
"napi4",
Expand Down
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,38 @@
# Faster JWT (rust bindings) with an (optional) LRU cache

---

## Benchmarks

Benchmarks were performed using [benchmark](https://www.npmjs.com/package/benchmark) package, against [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken), [jose](https://www.npmjs.com/package/jose) and [fast-jwt](https://www.npmjs.com/package/fast-jwt). It was not done to cast shadow over those packages, merely to check whether this package is performant enough or not.

Machine Details:

- OS: Ubuntu 22.04
- Processor: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz - Threads: 6 * 2
- Memory: 16G

**DISCLAIMER: Always take benchmarks like this with a grain of salt, as they may not always be indicative of good performance. And performance may not be the top thing to consider when choosing a package for solving your problem (unless the problem is that of performance itself). It would be best to perform these benchmarks on your own machine/deployment environment before making any decision.**

### Signing Benchmark ([bench/sign.mjs](./bench/sign.mjs))

```
jsonwebtoken x 1,982 ops/sec ±0.57% (189 runs sampled)
jose x 55,908 ops/sec ±0.78% (177 runs sampled)
fast-jwt x 52,656 ops/sec ±0.59% (186 runs sampled)
@carbonteq/jwt x 362,540 ops/sec ±0.35% (192 runs sampled)
@carbonteq/jwt#signClaims x 210,083 ops/sec ±2.29% (184 runs sampled)
SUITE <Sign token>: Fastest is @carbonteq/jwt
```

### Verifying Token ([bench/verify.mjs](./bench/verify.mjs))

```
jsonwebtoken x 2,068 ops/sec ±0.46% (187 runs sampled)
jose x 55,797 ops/sec ±0.29% (182 runs sampled)
fast-jwt x 68,474 ops/sec ±0.79% (182 runs sampled)
@carbonteq/jwt x 166,543 ops/sec ±0.73% (189 runs sampled)
SUITE <Verify Token>: Fastest is @carbonteq/jwt
```
55 changes: 10 additions & 45 deletions __test__/jwt-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,31 @@ import test from 'ava';
import * as jose from 'jose';

const secret = 'testsecretkeycanbeexposed';
const normalExpiresIn = 10000;
const secretEnc = new TextEncoder().encode(secret);
const client = new JwtClient(secret);

const testPayload = { user: 'test@carbonteq.dev' };

test('should create a valid token from payload', async (t) => {
const token = client.sign(testPayload, 10000);
const joseVerifyRes = await jose.jwtVerify(token, secretEnc);

t.deepEqual(joseVerifyRes.payload?.data, testPayload);
});

test('should create a valid token from claims', async (t) => {
const claims = new Claims(testPayload, 10000);
const token = client.signClaims(claims);
const token = client.sign(testPayload, normalExpiresIn);
const joseVerifyRes = await jose.jwtVerify(token, secretEnc);

t.deepEqual(joseVerifyRes.payload?.data, testPayload);
});

test('created token should be valid', (t) => {
const token = client.sign(testPayload, 10000);
const token = client.sign(testPayload, normalExpiresIn);

t.true(client.verify(token));
t.deepEqual(client.verify(token).data, testPayload);
});

test('created (claims) token should be valid', (t) => {
const claims = new Claims(testPayload, 10000);
const token = client.signClaims(claims);

t.true(client.verify(token));
});
test('verifying after exp should throw error', (t) => {
const claims = new Claims(testPayload, 1);
claims.exp = 10;

test('decode output should give the correct payload data', (t) => {
const token = client.sign(testPayload, 10000);
const decoded = client.verifyAndDecode(token);

t.deepEqual(decoded.data, testPayload);
});

test('decode output should give the correct payload data for claims', (t) => {
const claims = new Claims(testPayload, 10000);
const token = client.signClaims(claims);
const decoded = client.verifyAndDecode(token);

t.deepEqual(decoded.data, testPayload);
});

test('verifying after exp should return false', async (t) => {
const claims = new Claims(testPayload, 2);
claims.exp = 10; // In the past
const token = client.signClaims(claims);

t.false(client.verify(token));
});

test('decoding after exp should throw error', async (t) => {
const claims = new Claims(testPayload, 2);
claims.exp = 10; // In the past
const token = client.signClaims(claims);

t.throws(() => client.verifyAndDecode(token));
t.throws(() => {
client.verify(token);
});
});
73 changes: 0 additions & 73 deletions bench/decode.mjs

This file was deleted.

70 changes: 32 additions & 38 deletions bench/sign.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { JwtClient } from '../index.js';
import bench from 'benchmark';
import jwt from 'jsonwebtoken';
import chalk from 'chalk';
import fastJwt from 'fast-jwt';
import * as jose from 'jose';
import { Claims, JwtClient } from '../index.js';
import jwt from 'jsonwebtoken';

const suite = new bench.Suite();
const suite = new bench.Suite('Sign token');

const secret = 'somelongsecretasdbnakwfbjawf';
const minSamples = 100;
Expand All @@ -22,53 +23,46 @@ const joseSign = async (payload) => {
};

suite
.add('jsonwebtoken#sign', {
defer: true,
minSamples,
fn: function (deferred) {
.add(
'jsonwebtoken',
() => {
jwt.sign(payload, secret);
deferred.resolve();
},
})
.add('jose#sign', {
defer: true,
minSamples,
fn: function (deferred) {
joseSign(payload).then(() => deferred.resolve());
{ minSamples },
)
.add(
'jose',
async (deferred) => {
await joseSign(payload);
deferred.resolve();
// const s = new jose.SignJWT(payload);
// s.setProtectedHeader({ alg: "HS256" }).sign(encodedKey).then(defer);
},
})
.add('fastjwt#sign', {
defer: true,
minSamples,
fn: function (deferred) {
{ defer: true, minSamples },
)
.add(
'fast-jwt',
() => {
const signer = fastJwt.createSigner({ key: secret });
signer(payload);
deferred.resolve();
},
})
.add('@carbonteq/jwt#sign', {
defer: true,
minSamples,
fn: function (deferred) {
{ minSamples },
)
.add(
'@carbonteq/jwt',
() => {
client.sign(payload, 1000);
deferred.resolve();
},
})
.add('@carbonteq/jwt#signClaims', {
defer: true,
minSamples,
fn: function (deferred) {
const claims = new Claims(payload, 1000);
client.signClaims(claims);
deferred.resolve();
},
})
{ minSamples },
)
.on('cycle', (e) => {
console.log(String(e.target));
})
.on('complete', function () {
console.log('\nFastest is ' + this.filter('fastest').map('name'));
console.log(
`\nSUITE <${this.name}>: Fastest is ${chalk.green(
this.filter('fastest').map('name'),
)}`,
);
})
.run({ async: true });
.run();
Loading

0 comments on commit b32338d

Please sign in to comment.