1
0

Initial commit

This commit is contained in:
2021-02-04 12:02:42 +01:00
commit 6a17fb6be2
3 changed files with 195 additions and 0 deletions

67
README.md Normal file
View File

@@ -0,0 +1,67 @@
# Cloudflare Worker JWT
A lightweight JWT implementation with ZERO dependencies for Cloudflare Workers.
## Contents
- [Usage](#usage)
- [Install](#install)
## 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)
```
### `jwt.sign(payload, secret, [algorithm])`
Signs a payload and returns the token.
#### Parameters
`payload`
Can be an object, buffer or a string.
`secret`
A string which is used to sign the payload.
`algorithm` (optional, default: `HS256`)
The algorithm used to sign the payload, possible values: `HS256`(default) or `HS512`
### `jwt.verify(token, secret, [algorithm])`
Verifies the integrity of the token and returns a boolean value.
`token`
The token string generated by `jwt.sign()`.
`secret`
A string which is used to sign the payload.
`algorithm` (optional, default: `HS256`)
The algorithm used to sign the payload, possible values: `HS256`(default) or `HS512`
### `jwt.decode(token)`
Returns the payload without verifying the integrity of the token.
`token`
The token string generated by `jwt.sign()`.
## Install
```
npm i @tsndr/cloudflare-worker-jwt
```

102
index.js Normal file
View File

@@ -0,0 +1,102 @@
class Base64URL {
static parse(s) {
return new Uint8Array(Array.prototype.map.call(atob(s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')), c => c.charCodeAt(0)))
}
static stringify(a) {
return btoa(String.fromCharCode.apply(0, a)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}
}
class JWT {
constructor() {
if (!crypto || !crypto.subtle)
throw new Error('Crypto not supported!')
this.algorithms = {
HS256: {
name: 'HMAC',
hash: {
name: 'SHA-256'
}
},
HS512: {
name: 'HMAC',
hash: {
name: 'SHA-512'
}
}
}
}
utf8ToUint8Array(str) {
const chars = []
str = btoa(unescape(encodeURIComponent(str)))
return Base64URL.parse(str)
}
async sign(payload, secret, alg = 'HS256') {
if (payload === null || typeof payload !== 'object')
throw new Error('payload must be an object')
if (typeof secret !== 'string')
throw new Error('secret must be a string')
if (typeof alg !== 'string')
throw new Error('alg must be a string')
const importAlgorithm = this.algorithms[alg]
if (!importAlgorithm)
throw new Error('algorithm not found')
const payloadAsJSON = JSON.stringify(payload)
const partialToken = `${Base64URL.stringify(this.utf8ToUint8Array(JSON.stringify({ alg, 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()
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))
return `${partialToken}.${Base64URL.stringify(new Uint8Array(signature))}`
}
async verify(token, secret, alg = 'HS256') {
if (typeof token !== 'string')
throw new Error('token must be a string')
if (typeof secret !== 'string')
throw new Error('secret must be a string')
if (typeof alg !== 'string')
throw new Error('alg must be a string')
const tokenParts = token.split('.')
if (tokenParts.length !== 3)
throw new Error('token must have 3 parts')
const importAlgorithm = this.algorithms[alg]
if (!importAlgorithm)
throw new Error('algorithm not found')
const keyData = this.utf8ToUint8Array(secret)
const key = await crypto.subtle.importKey('raw', keyData, importAlgorithm, false, ['sign'])
const partialToken = tokenParts.slice(0, 2).join('.')
const signaturePart = tokenParts[2]
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
}
}
}
module.exports = new JWT

26
package.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "@tsndr/cloudflare-worker-jwt",
"version": "1.0.0",
"description": "A lightweight JWT implementation with ZERO dependencies for Cloudflare Worker",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tsndr/cloudflare-worker-jwt.git"
},
"keywords": [
"jwt",
"token",
"cloudflare",
"worker",
"cloudflare-worker"
],
"author": "Tobias Schneider",
"license": "ISC",
"bugs": {
"url": "https://github.com/tsndr/cloudflare-worker-jwt/issues"
},
"homepage": "https://github.com/tsndr/cloudflare-worker-jwt#readme"
}