@xeredo/crypto-auth
Authenticate HTTP requests using cryptographic signatures
Supports hapi and fetch
NOTE: Keys are using @xeredo/easy-crypto
If you prefer using node's KeyObject, just use require('@xeredo/crypto').private/public.fromKeyObject(key)
where necesarry
Usage
Client
'use strict'
async function main () {
const ec = require('@xeredo/easy-crypto')
// generate some keys
const pair = ec.generateKeyPair('rsa', 4096)
const Auth = require('@xeredo/crypto-auth')
const client = Auth.client(pair)
const fetch = require('node-fetch')
console.log(await (await fetch('http://localhost:3333/registerClient', {
method: 'POST',
// export key as PEM
body: new URLSearchParams({ key: pair.pub.export() })
})).json())
// send a signed request
const req = await fetch(...client.signFetch('http://localhost:3333/hello', 'POST', { name: 'Johnson' }))
// get result
const res = await req.json()
console.log(res)
}
main().then(() => {}, console.error)
Server
'use strict'
const Hapi = require('@hapi/hapi')
const Auth = require('@xeredo/crypto-auth')
const Joi = require('joi')
const ec = require('@xeredo/easy-crypto')
const init = async () => {
const server = Hapi.server({
port: 3333,
host: 'localhost',
debug: { request: ['error'] }
})
// you want to use a database instead
const devices = {}
await server.register({
plugin: Auth.hapi
})
server.auth.strategy('session', 'crypto-auth', {
getDevice (fingerprint) {
console.log('authorizing', fingerprint)
if (devices[fingerprint]) {
return devices[fingerprint]
}
}
})
server.auth.default({
strategy: 'session',
payload: true
})
server.route({
method: 'POST',
path: '/registerClient',
config: {
auth: false,
validate: {
payload: Joi.object({
key: Joi.string().required()
})
},
handler: async (request, h) => {
const key = ec.public.fromBuffer(request.payload.key)
const fingerprint = key.spkiFingerprint()
console.log('added device', fingerprint)
devices[fingerprint] = {
key,
credentials: {
fingerprint,
// user-id
id: String(Math.random())
}
}
return { added: fingerprint }
}
}
})
server.route({
method: 'POST',
path: '/hello',
config: {
validate: {
payload: Joi.object({
name: Joi.string().required()
})
},
handler: async (request, h) => {
return { msg: `Hello ${request.payload.name}`, credentials: request.auth.credentials }
}
}
})
await server.start()
console.log('Server running on %s', server.info.uri)
}
process.on('unhandledRejection', (err) => {
console.log(err)
process.exit(1)
})
init()
API
Client
client(key<EasyCrypto.PrivateKey>)
-
.signFetch(url<string>, method<string>, body<Object>)
- Sign a request and return parameters for node-fetch or window.fetch
-
.sign(url<string>, method<string>, body<Object>)
- Sign a request and return headers. You can use this if you want to use other libraries instead of fetch
-
.fingerprint
- SPKI Fingerprint of the key in hex
Server
.hapi
-
tolerance<Integer>
: Tolerate this much difference in timestamp. Default: 5 minutes. -
getKey(fingerprint<string>)
: Function that will be used to retrive a key- Expected to return object with keys:
.key<EasyCrypto.PublicKey>
-
.credentials<object>
: Optional, extra details for hapi credentials object - or undefined if key not found
- Throw @hapi/boom errors only