Search code examples
ruby-on-railsrubyjwtauth0ruby-on-rails-4.2

Error reproducing Auth0 instructions on “Ruby On Rails API: Authentication”


I used the quick start “Ruby On Rails API: Authentication” successfully. Created an API and everything works just fine. Then I tried to recreate the code but including all this on an existing Rails app and got an error.

The demo (working for me before) include this call to the JWT gem:

JWT.decode(token, nil, # <----
    true, # Verify the signature of this token
    algorithm: "RS256",
    iss: "https://" + ENV["AUTH0_DOMAIN"],
    verify_iss: true,
    aud: ENV["AUTH0_AUDIENCE"],
    verify_aud: true)

The second parameter with nil assigned, in the demo works, but in my project leads me to a JWT Error, understanding that the parameter used for public_key shouldn’t be null. But it’s null in the demo and works(UPDATE: The nil message seems to come from another object reference). I’m kind of a newbie with JWT matters.

Error message:

undefined method `verify' for nil:NilClass
  /usr/local/bundle/gems/jwt-2.2.1/lib/jwt/security_utils.rb:20:in `verify_rsa'

-------------------------------
Backtrace:
-------------------------------

  /usr/local/bundle/gems/jwt-2.2.1/lib/jwt/security_utils.rb:20:in `verify_rsa'
  /usr/local/bundle/gems/jwt-2.2.1/lib/jwt/algos/rsa.rb:15:in `verify'
  /usr/local/bundle/gems/jwt-2.2.1/lib/jwt/signature.rb:44:in `verify'
  /usr/local/bundle/gems/jwt-2.2.1/lib/jwt/decode.rb:42:in `verify_signature'
  /usr/local/bundle/gems/jwt-2.2.1/lib/jwt/decode.rb:26:in `decode_segments'
  /usr/local/bundle/gems/jwt-2.2.1/lib/jwt.rb:28:in `decode'
  /api/lib/json_web_token.rb:10:in `verify'
  /api/app/controllers/concerns/secured.rb:71:in `auth_token'
  /api/app/controllers/concerns/secured.rb:50:in `authenticate_request!'

json_web_token.rb and secured.rb are identical to the sample. jwt-2.2.1 it's the same gem version used in both environments.

The only difference I found between the environments of the demo and my legacy project was the Rails version, 5 in the demo, and 4.x on mine. I can’t upgrade right now, so if you think in another thing I can be doing wrong it would be helpful.

Update 2: Including sample code

I'm including here the code for the jwks_hash method, it's the same in both environments. I just added logger lines to the original. You can see the differences in the output for both environments below.

def self.jwks_hash
    Rails.logger.warn '---> hashing'
    Rails.logger.warn "https://#{Rails.application.secrets.auth0_domain}/.well-known/jwks.json"
    jwks_raw = Net::HTTP.get URI("https://#{Rails.application.secrets.auth0_domain}/.well-known/jwks.json")
    Rails.logger.warn jwks_raw
    jwks_keys = Array(JSON.parse(jwks_raw)['keys'])
    Rails.logger.warn '---> jwks_keys'
    Rails.logger.warn jwks_keys  # Check output of this below 👇
    Hash[
      jwks_keys
      .map do |k|
        [
          k['kid'],
          OpenSSL::X509::Certificate.new(
            Base64.decode64(k['x5c'].first)
          ).public_key
        ]
      end
    ]
  end

Output in working demo:

W, [...]  WARN -- : [{"alg"=>"RS256", "kty"=>"RSA", "use"=>"sig", "n"=>"yDsoJbr45jlrCDQSu8X6ZAko......"]}]

W, [...]  WARN -- : {"alg"=>"RS256", "typ"=>"JWT", "kid"=>"YHEH94lH9itYZUhLx3hee"}

Output in my project:

W, [...]  WARN -- : [{"alg"=>"RS256", "kty"=>"RSA", "use"=>"sig", "n"=>"yDsoJbr45jlrCDQSu8X6ZAko......"]}]

W, [...]  WARN -- : [{"alg"=>"RS256", "kty"=>"RSA", "use"=>"sig", "n"=>"yDsoJbr45jlrCDQSu8X6ZAko......"]}]

The second element it's the only point of difference between the runs I could found.


Solution

  • I solved the situation with a workaround. I used the gem json-jwt instead of jwt and modify my code ends like this:

    expected_iss = 'https://'+ENV['AUTH0_DOMAIN']+'/'
    expected_aud = ENV['AUTH0_AUDIENCE']
    
    begin
        jwk_set = JSON::JWK::Set.new(
            JSON.parse(Net::HTTP.get URI(ENV['AUTH0_RSA_DOMAIN']))
            )
        id_token = JSON::JWT.decode token, jwk_set
        unless (id_token[:iss] == expected_iss) # && id_token[:aud] == expected_aud)
        #raise 'ID Token Verification Failed!'
        false
    end