Skip to content

Commit

Permalink
Added nbf and exp support
Browse files Browse the repository at this point in the history
  • Loading branch information
tsndr committed May 23, 2021
1 parent e34e1ae commit 8d9f70c
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 45 deletions.
65 changes: 46 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ A lightweight JWT implementation with ZERO dependencies for Cloudflare Workers.

- [Install](#install)
- [Usage](#usage)
- [Examples](#examples)

## Install

Expand All @@ -15,21 +16,6 @@ npm i @tsndr/cloudflare-worker-jwt

## Usage

### Simple Example

```javascript
const jwt = require('@tsndr/cloudflare-worker-jwt')

// Creating a token
const token = jwt.sign({ name: 'John Doe', email: 'john.doe@gmail.com' }, 'secret')

// Verifing token
const isValid = jwt.verify(token, 'secret')

// Decoding token
const payload = jwt.decode(token)
```

<hr>

### `jwt.sign(payload, secret, [algorithm])`
Expand All @@ -40,7 +26,7 @@ Signs a payload and returns the token.

Argument | Type | Satus | Default | Description
----------- | -------- | -------- | ------- | -----------
`payload` | `object` | required | - | The payload object
`payload` | `object` | required | - | The payload object. To use `nbf` (Not Before) and/or `exp` (Expiration Time) add `nbf` and/or `exp` to the payload.
`secret` | `string` | required | - | A string which is used to sign the payload.
`algorithm` | `string` | optional | `HS256` | The algorithm used to sign the payload, possible values: `HS256` or `HS512`

Expand All @@ -60,17 +46,58 @@ Argument | Type | Satus | Default | Description
`algorithm` | `string` | optional | `HS256` | The algorithm used to sign the payload, possible values: `HS256` or `HS512`

#### `return`
returns `boolean`
Returns `true` if signature, `nbf` (if set) and `exp` (if set) are valid, otherwise returns `false`.

<hr>

### `jwt.decode(token)`

Returns the payload without verifying the integrity of the token.
Returns the payload **without** verifying the integrity of the token. Please use `jwt.verify()` first to keep your application secure!

Argument | Type | Satus | Default | Description
----------- | -------- | -------- | ------- | -----------
`token` | `string` | required | - | The token string generated by `jwt.sign()`.

#### `return`
returns payload `object`
Returns payload `object`.

## Examples

### Basic Example

```javascript
async () => {
const jwt = require('@tsndr/cloudflare-worker-jwt')

// Creating a token
const token = await jwt.sign({ name: 'John Doe', email: 'john.doe@gmail.com' }, 'secret')

// Verifing token
const isValid = await jwt.verify(token, 'secret')

// Decoding token
const payload = jwt.decode(token)
}
```

### Restrict Timeframe

```javascript
async () => {
const jwt = require('@tsndr/cloudflare-worker-jwt')

// Creating a token
const token = await jwt.sign({
name: 'John Doe',
email: 'john.doe@gmail.com',
nbf: Math.floor(Date.now() / 1000) + (60 * 60), // Not before: Now + 1h
exp: Math.floor(Date.now() / 1000) + (2 * (60 * 60)) // Expires: Now + 2h
}, 'secret')

// Verifing token
const isValid = await jwt.verify(token, 'secret') // false

// Decoding token
const payload = jwt.decode(token) // { name: 'John Doe', email: 'john.doe@gmail.com', ... }
}
```
58 changes: 33 additions & 25 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,28 @@ class JWT {
}
}
}
utf8ToUint8Array(str) {
_utf8ToUint8Array(str) {
return Base64URL.parse(btoa(unescape(encodeURIComponent(str))))
}
_decodePayload(raw) {
switch (raw.length % 4) {
case 0:
break
case 2:
raw += '=='
break
case 3:
raw += '='
break
default:
throw new Error('Illegal base64url string!')
}
try {
return JSON.parse(decodeURIComponent(escape(atob(raw))))
} catch {
return null
}
}
async sign(payload, secret, algorithm = 'HS256') {
if (payload === null || typeof payload !== 'object')
throw new Error('payload must be an object')
Expand All @@ -39,19 +58,20 @@ class JWT {
const importAlgorithm = this.algorithms[algorithm]
if (!importAlgorithm)
throw new Error('algorithm not found')
payload.iat = Math.floor(Date.now() / 1000)
const payloadAsJSON = JSON.stringify(payload)
const partialToken = `${Base64URL.stringify(this.utf8ToUint8Array(JSON.stringify({ alg: algorithm, typ: 'JWT' })))}.${Base64URL.stringify(this.utf8ToUint8Array(payloadAsJSON))}`
const key = await crypto.subtle.importKey('raw', this.utf8ToUint8Array(secret), importAlgorithm, false, ['sign'])
const partialToken = `${Base64URL.stringify(this._utf8ToUint8Array(JSON.stringify({ alg: algorithm, typ: 'JWT' })))}.${Base64URL.stringify(this._utf8ToUint8Array(payloadAsJSON))}`
const key = await crypto.subtle.importKey('raw', this._utf8ToUint8Array(secret), importAlgorithm, false, ['sign'])
const characters = payloadAsJSON.split('')
const it = this.utf8ToUint8Array(payloadAsJSON).entries()
const it = this._utf8ToUint8Array(payloadAsJSON).entries()
let i = 0
const result = []
let current
while (!(current = it.next()).done) {
result.push([current.value[1], characters[i]])
i++
}
const signature = await crypto.subtle.sign(importAlgorithm.name, key, this.utf8ToUint8Array(partialToken))
const signature = await crypto.subtle.sign(importAlgorithm.name, key, this._utf8ToUint8Array(partialToken))
return `${partialToken}.${Base64URL.stringify(new Uint8Array(signature))}`
}
async verify(token, secret, algorithm = 'HS256') {
Expand All @@ -67,33 +87,21 @@ class JWT {
const importAlgorithm = this.algorithms[algorithm]
if (!importAlgorithm)
throw new Error('algorithm not found')
const keyData = this.utf8ToUint8Array(secret)
const keyData = this._utf8ToUint8Array(secret)
const key = await crypto.subtle.importKey('raw', keyData, importAlgorithm, false, ['sign'])
const partialToken = tokenParts.slice(0, 2).join('.')
const payload = this._decodePayload(tokenParts[1].replace(/-/g, '+').replace(/_/g, '/'))
if (payload.nbf && payload.nbf >= Math.floor(Date.now() / 1000))
return false
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000))
return false
const signaturePart = tokenParts[2]
const messageAsUint8Array = this.utf8ToUint8Array(partialToken)
const messageAsUint8Array = this._utf8ToUint8Array(partialToken)
const res = await crypto.subtle.sign(importAlgorithm.name, key, messageAsUint8Array)
return Base64URL.stringify(new Uint8Array(res)) === signaturePart
}
decode(token) {
let output = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')
switch (output.length % 4) {
case 0:
break
case 2:
output += '=='
break
case 3:
output += '='
break
default:
throw new Error('Illegal base64url string!')
}
try {
return JSON.parse(decodeURIComponent(escape(atob(output))))
} catch {
return null
}
return this._decodePayload(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))
}
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tsndr/cloudflare-worker-jwt",
"version": "1.0.8",
"version": "1.1.0",
"description": "A lightweight JWT implementation with ZERO dependencies for Cloudflare Worker",
"main": "index.js",
"scripts": {
Expand Down

0 comments on commit 8d9f70c

Please sign in to comment.