passport-jwt icon indicating copy to clipboard operation
passport-jwt copied to clipboard

Multiple secrets/keys

Open saiichihashimoto opened this issue 7 years ago • 5 comments

With something like heroku's securekey addon, they have a rotating key, giving you the key and the previous key. I'd like to be able to check against either. Is that possible with passport-jwt?

saiichihashimoto avatar Jan 24 '18 02:01 saiichihashimoto

This is not directly supported today but I see the use case. I believe Google supports a similar rotating key strategy that would require validating against multiple keys (i.e. the current key and the last used key).

You could potentially shoehorn it in by overriding the JwtVerifier. I'd be open to a pull request for this feature.

Without really digging into this, I think it could be accomplished in a backwards compatible fashion by accepting either a single secret or key or an array of secrets/keys from the secretOrKeyProvider callback. Decoding could be tried against each key returned until successful or you run out of keys.

mikenicholson avatar Nov 03 '18 18:11 mikenicholson

Decoding could be tried against each key returned until successful or you run out of keys.

Note that JWTs have a kid (Key ID) header for this purpose. Instead of a list of secrets a map of key ids to secrets could be provided instead.

dantman avatar Feb 23 '19 05:02 dantman

There's a nice explanation here about how key rotation is handled with JWKS: https://auth0.com/blog/navigating-rs256-and-jwks

dsebastien avatar Jul 30 '19 16:07 dsebastien

Here is some code that i use to handle this situation.


let secretOrKeyProvider = (req, token, done) => {
  const beginCert = '-----BEGIN CERTIFICATE-----'
  const endCert = '-----END CERTIFICATE-----'
  const keys = opts.keys
  var decodedToken = decode(token, { complete: true })
  if (!decodedToken) {
    done(`Error: malformed token: ${decodedToken}`, null)
    return
  }
  const key = keys.find(k => k.kid == decodedToken.header.kid)
  if (!key) {
    done(`Error: key not found: ${decodedToken.header.kid}`, null)
    return
  }
  const secretOrKey = `${beginCert}\n${key.x5c[0]}\n${endCert}`
  done(null, secretOrKey)
}

const opts = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  issuer: process.env.IDP_URL,
  passReqToCallback: true,
  secretOrKeyProvider: secretOrKeyProvider,
  algorithms: []
}

let idp = create({
  baseURL: process.env.IDP_URL || "http://{{your.openid.connect.provider"
})

// called only once at application start.
idp.get('/.well-known/openid-configuration').then(openid => {
    var openidConfig = openid.data
    opts.openidConfig = openidConfig
    idp.get(opts.openidConfig.jwks_uri).then(jwks => {
        opts.keys = jwks.data.keys
        opts.keys.forEach(key => opts.algorithms.push(key.alg))
        passport.use(new JwtStrategy(opts, (req, jwt_payload, done) => {
            done(null, jwt_payload)
          })
        )
      }).catch(err => {
        console.error(err)
      })
  }).catch(err => {
    console.error(err)
  })

Hope this helps!

sbaker avatar Sep 07 '19 01:09 sbaker

This has already been implemented. Check out https://github.com/auth0/node-jwks-rsa

niksauer avatar Oct 14 '19 16:10 niksauer