Skip to content

Instantly share code, notes, and snippets.

@fxg42
Last active February 20, 2017 15:00
Show Gist options
  • Save fxg42/ad2f802c61749b4fd6e4f95a868b7feb to your computer and use it in GitHub Desktop.
Save fxg42/ad2f802c61749b4fd6e4f95a868b7feb to your computer and use it in GitHub Desktop.
Signed token generation with Node
/*
* Copyright 2017 CODE3 Coopérative de solidarité
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import crypto from 'crypto'
import fs from 'fs'
// Utilities
//
const promisify = (f) => (...args) => new Promise((resolve, reject) => f(...args, (err, val) => err ? reject(err) : resolve(val)))
const readFile = promisify(fs.readFile)
// Generates a token containing a plaintext base64 encoded JSON object and the
// same object signed by the application. The object holds some data and a
// timestamp.
//
const sign = (data, privateKey) => {
const message = JSON.stringify({ data:data, signed:Date.now() })
const plaintext = new Buffer(message).toString('base64')
const signer = crypto.createSign('RSA-SHA256')
const signature = signer.update(plaintext).sign(privateKey, 'base64')
return `${plaintext}.${signature}`
}
// Verifies a token generated by `sign` with the corresponding public key and a
// time limit. Returns the original data or `false` if the token is invalid or
// expired.
//
const verify = (token, publicKey, maxAge) => {
try {
const [plaintext, signature] = token.split('.', 2)
const verifier = crypto.createVerify('RSA-SHA256').update(plaintext)
if (! verifier.verify(publicKey, signature, 'base64')) return false // invalid token
const message = JSON.parse(Buffer.from(plaintext, 'base64').toString('utf-8'))
return !maxAge || (Date.now() < message.signed + maxAge) ? message.data : false // expired token
} catch (e) {
return false
}
}
const main = async () => {
const privateKey = await readFile('./privkey.pem') //=> $ openssl genrsa -out ./privkey.pem 2048
const publicKey = await readFile('./pubkey.pem') //=> $ openssl rsa -in ./privkey.pem -pubout -out ./pubkey.pem
const token = sign({id:1}, privateKey)
const data = verify(token, publicKey)
console.log(data)
}
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment