npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@connectedcars/jwtutils

v2.0.3

Published

Zero dependency JWT encoding/decoding for Node

Downloads

532

Readme

node-jwtutils

Build Status Coverage Status

Zero dependency JWT encoding and decoding for Node.

Features:

  • Encode and decode any JWT tokens
  • Supported asymmetric algorithmes (RS256, RS384, RS512, ES256, ES384 and ES512)
  • Supported symmetric algorithmes (HS256, HS384 and HS512)
  • Support for multiple issuers and keys per issuer.
  • Import keys from JWK endpoints
  • Easy service authentication for Google and Github
  • Express middleware to validate JWT's

Background

Most other JWT implementations tend be complex and have a large array of dependencies because they implement the seldom used JOSE standard. This often makes the whole JWT encoding/decoding complicated and hard to understand, something it really should not be.

So the focus of this module has been to make a simple, secure, fast and flexible set of utility methods to work with JWT's. The code itself is also easy to understand and less than 200 lines of code, making it much easier to security audit the code.

Also note doing your own crypto is a bad idea so this module only deals with the encoding/decoding of the JWT, the underlaying crypto operations are done by Node's build-in crypto api that uses openssl.

While symmetric algorithms are supported for legacy integrations, they are not advisable for production use.

Samples

Basic usage

const { JwtUtils, JwtVerifyError } = require('@connectedcars/jwtutils')

const unixNow = Math.floor(Date.now() / 1000)

let jwtHeader = {
  typ: 'JWT',
  alg: 'RS256',
  kid: '1'
}

let jwtBody = {
  aud: 'https://api.domain.tld',
  iss: 'https://jwt.io/',
  sub: '[email protected]',
  iat: unixNow,
  exp: unixNow + 600,
  scope: ['http://stuff', 'http://stuff2']
}

// Don't use this key for anything but testing as this is the key from jwt.io
const pemEncodedPrivateKey =
  '-----BEGIN RSA PRIVATE KEY-----\n' +
  'MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw\n' +
  '33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW\n' +
  '+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB\n' +
  'AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS\n' +
  '3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp\n' +
  'uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE\n' +
  '2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0\n' +
  'GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K\n' +
  'Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY\n' +
  '6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5\n' +
  'fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523\n' +
  'Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP\n' +
  'FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==\n' +
  '-----END RSA PRIVATE KEY-----'

// let jwt = JwtUtils.encode(pemEncodedPrivateKey, jwtHeader, jwtBody, privateKeyPassword)
let jwt = JwtUtils.encode(pemEncodedPrivateKey, jwtHeader, jwtBody)

// Don't use this key for anything but testing as this is the key from jwt.io
const publicKey =
  '-----BEGIN PUBLIC KEY-----\n' +
  'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd\n' +
  'UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs\n' +
  'HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D\n' +
  'o2kQ+X5xK9cipRgEKwIDAQAB\n' +
  '-----END PUBLIC KEY-----'

const allowedAudinces = ['https://api.domain.tld']

const pubKeys = {
  'https://jwt.io/': {
    '1@RS256': publicKey,
    'default@RS256': publicKey // Will default to this key if the header does not have a kid
  }
}

try {
  let decodedJwtBody = JwtUtils.decode(jwt, pubKeys, allowedAudinces)
} catch (e) {
  if (e instanceof JwtVerifyError) {
    // Can be returned to user
  } else {
    // Should not be returned to user
    console.error(e)
  }
}

Usage of express middleware

const express = require('express')
const { JwtAuthMiddleware, JwtVerifyError } = require('@connectedcars/jwtutils')

// Configuration
const audiences = ['https://api.domain.tld']
const pubKeys = {
  'https://jwt.io/': {
    '1@RS256': publicKey // Fx. use key from before
  },
  'https://jwt.io/custom': { // Overwrite default validation for this issuer
    '1@RS256': { // Same options can also be used directly with decode
      publicKey: publicKey,
      expiresMax: 3600, // Don't allow token that has a lifetime over 1 hour
      expiresSkew: 600, // Allow tokens that expired up to 10 minutes ago
      nbfIatSkew: 300, // Allow tokens that has nbf or iat in the future by up to 5 minutes
    }
  }
}

const app = express()

// Register the middleware
app.use(JwtAuthMiddleware(pubKeys, audiences, user => { // Also supports Promises and async
  if (user.issuer === 'https://jwt.io/') {
    if (!user.subject.match(/^[^@]+@domain\.tld$/)) {
      throw new JwtVerifyError('Issuer https://jwt.io/ only allowed to have subject ending in @domain.tld')
    }
  }
}))

// Register an error handler to return 401 errors
app.use((err, req, res, next) => {
  if (err instanceof JwtVerifyError) {
    if (err.innerError) {
      console.error(`Failed with: ${err.innerError.message}`)
    }
    res.status(401).send(err.message)
  } else {
    res.status(500).send('Unknown error')
  }
})

app.get('/', (req, res) => {
  res.send(`Hello ${req.user.subject}`)
})
app.listen(3000, () => {
  console.log('Example app listening on port 3000!')
})

Integration with 3rd parties

Google Identity platform:

const { PubkeysHelper } = require('@connectedcars/jwtutils')

// Fetch with Google's public keys every hour
const applyGoogleKeys = (pubKeys) => {
  PubkeysHelper.fetchJwkKeys('https://www.googleapis.com/oauth2/v3/certs')
    .then(keys => {
      pubKeys['https://accounts.google.com'] = keys
    })
    .catch(e => {
      // Do some error handling
      console.log(e)
    })
}

applyGoogleKeys(pubKeys)
setInterval(() => {
  applyGoogleKeys(pubKeys)
}, 60 * 60 * 1000)

Usage of service authentication (Google and Github)

const { JwtServiceAuth } = require('./index')
const fs = require('fs')
const r2 = require('r2')

// Wrap your favorite http library
let httpRequestHandler = async (method, url, headers, body) => { // Fx. POST, http://domain.tld, {}, "..."
  // Do http request
  let httpRespone = await r2[method.toLowerCase()](url, { headers, body }).response
  let data = await httpResponse.arrayBuffer()
  return {
    statusCode: httpResponse.status,
    data: data,
    headers: httpResponse.headers
  }
})

let jwtServiceAuth = new JwtServiceAuth(httpRequestHandler) // Or use the built-in default

let gitHubAppPrivateKey = fs.readFileSync("user-appname.2017-01-01.private-key.pem", 'utf8')
let googleServiceAccountKeyfile = fs.readFileSync("user-serviceaccount-12345678.json", 'utf8')

async function getAccessTokens() {
  let githubAppToken = await = jwtServiceAuth.getGithubAccessToken(gitHubAppPrivateKey, 1, 1)
  let googleToken = await jwtServiceAuth.getGoogleAccessToken(googleServiceAccountKeyfile)
}

Documentation

  • Github App: https://developer.github.com/apps/building-github-apps/authentication-options-for-github-apps/
  • Google Service Account: https://developers.google.com/identity/protocols/OAuth2ServiceAccount

Generate own keypair

Generate private RSA key:

# Here the key is encrypted with aes256
openssl genrsa -aes256 -out private.pem 2048

Generate public key from private key:

openssl rsa -in private.pem -outform PEM -pubout -out public.pem

Command line helper utils

NOTE: Does not support nested JSON

Load private key:

jwtencode private.pem

Copy/paste to stdin (Ctrl-D to end), the password line is only needed if the private key is encrypted:

password password-for-private-key
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "1"
}
{
  "iss": "jwt.io",
  "aud": "https://api.domain.tld",
  "sub": "[email protected]",
  "iat": 1504292127,
  "nbf": 1504292127,
  "exp": 1598986470
}
jwtdecode public.pem 1 RS256 https://jwt.io localhost

Copy/paste to stdin (Ctrl-D to end):

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJpc3MiOiJodHRwczovL2p3dC5pbyIsImF1ZCI6ImxvY2FsaG9zdCIsInN1YiI6InN1YmplY3RAZG9tYWluLnRsZCIsImlhdCI6MTUwNDI5MjEyNywibmJmIjoxNTA0MjkyMTI3LCJleHAiOjE1OTg5ODY0NzB9.0L5AWwUF3EleBqnQ6V0Lqa36jCccP4A7cAFHHIY1b-oE7pxCoFr8gnAOrlc16N0WUPI6O17JT79kQIPR-LjFm-BgBycBw4eEFYb8z7iXA-zqgQz4ajZXlIljJtJUBbTupbnzEiBKjEFnTxYqb-vUm-TDwTMPaYzBxqqfOrrvKlw