mikenicholson / passport-jwt

Passport authentication using JSON Web Tokens
MIT License
1.96k stars 214 forks source link

Multiple secrets/keys #143

Closed saiichihashimoto closed 1 year ago

saiichihashimoto commented 6 years ago

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?

mikenicholson commented 5 years ago

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.

dantman commented 5 years ago

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.

dsebastien commented 5 years ago

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

sbaker commented 5 years ago

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!

niksauer commented 5 years ago

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