Initial commit
This commit is contained in:
67
README.md
Normal file
67
README.md
Normal 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
102
index.js
Normal 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
26
package.json
Normal 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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user