omniauth / omniauth_openid_connect

MIT License
171 stars 187 forks source link

Support verification of HS256-signed JWTs #134

Closed stanhu closed 1 year ago

stanhu commented 2 years ago

When an OpenID provider such as Keycloak is configured to use the HS256 algorithm to sign JSON Web Tokens (JWTs), previously logins would fail with an obscure error:

JSON::JWS::UnexpectedAlgorithm (no implicit conversion of OpenSSL::PKey::RSA into String)

The HS256 algorithm relies on a shared secret between the client and the server, and this secret is not in the list of JSON Web Key Set (JWKS) that is typically retrieved via a public endpoint during OpenID Discovery.

The error was happening because decoding a HS256-signed JWT with public key RSA key will fail. We should only attempt to decode with the configured client_options.secret.

To do this, we need to decode the JWT to examine the header to determine how it was signed. For example:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "RrHQomcBaw2FJZ9Q5skkNaPC6sHJFLUAf4uoeaItDPE"
}

This indicates that the payload was signed with RS256, a public key algorithm.

Once we know the algorithm used to decode, we can determine which key to use. For public key algorithms such as RS256, we use the JWKS. For signature-based algoriths such as HS256, we use the configured client secret.

This merge request also cleans up some technical debt. Previously we didn't peek at the unverified JWT to determine the key ID (kid) that the payload was signed with. If we got a KidNotFound exception from the decoding, previously we couldn't tell whether this was happening because we didn't have a matching key in JWKS, or whether the JWT didn't have a kid to begin with. Now that we decode the header, we can tell these cases apart. If the JWT has no kid and we get KidNotFound from decoding, we know the server didn't supply the right set of keys. Otherwise, we can just use try key until we find one that works.

Relates to https://gitlab.com/gitlab-org/ruby/gems/gitlab-omniauth-openid-connect/-/issues/1

This is part of the effort to upstream changes in the GitLab fork: https://gitlab.com/gitlab-org/ruby/gems/gitlab-omniauth-openid-connect/-/issues/5.