Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid JWT token type (invalid_credentials: OAuth2::Error) #157

Open
dombarnes opened this issue Jul 16, 2022 · 5 comments
Open

Invalid JWT token type (invalid_credentials: OAuth2::Error) #157

dombarnes opened this issue Jul 16, 2022 · 5 comments

Comments

@dombarnes
Copy link

I am currently using this gem in a couple of Rails projects without issue connecting to an IdentityServer with openid/oauth.
I was previously using a fairly basic handwritten Oauth handler in a modular sinatra project and wanted to migrate to this gem to be consistent.
I transferred over my strategy but when I log in now, I hit my oauth server requesting an id_token code method, get back a response with a code. That is then passed to the token endpoint and I get back a response with an id_tokenand an access_token. However when I call access_token.get('connect/userinfo')to get my user info, the HTTP call seems to be using the id_tokennot the access_token and so I am getting a 401 unauthorised error.
I can't seem to figure out what's going wrong.

my_company.rb
# frozen_string_literal: true

require 'omniauth-oauth2'
require 'jwt'
require 'securerandom'

module OmniAuth
  module Strategies
    class MyCompany < OmniAuth::Strategies::OAuth2

      args %i[client_id client_secret client_scope]

      option :name, 'my_company'
      option :client_options, {
        site: ENV.fetch('OAUTH_SERVER'),
        authorize_url: '/connect/authorize',
        redirect_uri: "#{ENV['APPLICATION_HOST']}/auth/my_company/callback",
        token_url: '/connect/token'
      }

      option :authorize_params, {
        nonce: SecureRandom.hex(24),
        scope: ENV.fetch('OAUTH_SCOPE', 'openid profile'),
        response_mode: 'form_post',
        response_type: 'code id_token'
      }

      uid { raw_info['sub'] }

      info do
        {
          email: raw_info['email'],
          displayname: raw_info['displayname'],
          role: raw_info['role']
        }
      end

      extra do
        hash = {}
        hash[:raw_info] = raw_info unless skip_info?
        hash[:id_token] = access_token.token
        if !options[:skip_jwt] && !access_token.token.nil?
          hash[:id_info] = validated_token(access_token.token)
        end
        hash
      end

      def callback_url
        options[:redirect_uri] || (full_host + script_name + callback_path)
      end

      def raw_info
        @raw_info ||= access_token.get('connect/userinfo').parsed
      end

      def authorize_params
        super.merge(nonce: new_nonce)
      end

      alias oauth2_access_token access_token

      def access_token
        ::OAuth2::AccessToken.new(client, oauth2_access_token.token, {
          refresh_token: oauth2_access_token.refresh_token,
          expires_in: oauth2_access_token.expires_in,
          expires_at: oauth2_access_token.expires_at
        })
      end

      private

      def new_nonce
        session['omniauth.nonce'] = SecureRandom.urlsafe_base64(16)
      end

      def stored_nonce
        session.delete('omniauth.nonce')
      end

      def verify_options
        { verify_expiration: true,
          verify_not_before: true,
          verify_iat: true,
          verify_iss: true,
          'iss' => issuer,
          verify_aud: true,
          'aud' => client_id }
      end
    end
  end
end

OmniAuth.config.add_camelization 'my_company', 'MyCompany'

Token response

{"id_token":"my_id_token","access_token":"my_access_token","expires_in":3600,"token_type":"Bearer","refresh_token":"my_refresh_token","scope":"openid profile offline_access"}

As such, its not even hitting my callback URL as Puma is catching the error.

Puma caught this error:  (OAuth2::Error)
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/oauth2-2.0.6/lib/oauth2/client.rb:139:in `request'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/oauth2-2.0.6/lib/oauth2/access_token.rb:140:in `request'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/oauth2-2.0.6/lib/oauth2/access_token.rb:147:in `get'
/Users/myuser/workprojects/myproject/lib/omni_auth/strategies/my_company.rb:55:in `raw_info'
/Users/myuser/workprojects/myproject/lib/omni_auth/strategies/my_company.rb:42:in `block in <class:MyCompany>'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:109:in `instance_eval'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:109:in `block in compile_stack'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:108:in `each'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:108:in `inject'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:108:in `compile_stack'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:102:in `extra_stack'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:387:in `extra'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:392:in `auth_hash'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:417:in `callback_phase'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-oauth2-1.8.0/lib/omniauth/strategies/oauth2.rb:93:in `callback_phase'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:272:in `callback_call'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:194:in `call!'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:169:in `call'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:202:in `call!'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/strategy.rb:169:in `call'
/Users/myuser/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/omniauth-2.1.0/lib/omniauth/builder.rb:44:in `call'
@BobbyMcWho
Copy link
Member

What version of this gem are you on?

@dombarnes
Copy link
Author

* omniauth-oauth2 (1.8.0)

@dombarnes
Copy link
Author

I think its being caused by this change in the oauth2 gem https://github.com/oauth-xx/oauth2/pull/621/files#diff-b665e6fc2096be34f9c5e92cb0f38eb3a229da37ed4da92487f0a834eb6ae336R53 as per discussion
At least reverting the version in my gemfile to 2.0.5 gets passed this error.

@BobbyMcWho
Copy link
Member

Okay, report the issue to them if you wouldn't mind, we'll have to PR here to pin the version below the breaking one

@dombarnes
Copy link
Author

After a lot of testing I found a solution to my issue, but not entirely sure of the cause. I think it was a combination of rack middleware misconfiguring, and double-loading my strategy. At one point I was getting errors about not providing an oauth Client ID from my provider and tracked back the issue.

For some reference tho:

Summary:
I have a modular Sinatra-based app and had previously written a custom OAuth implementation using the Oauth2 gem. When trying to switch to Omniauth, I had AuthenticityToken errors. I was using rack_encrypted_cookie instead of the standard Rack::Session::Cookie middleware, imported with use Rack::Session::EncryptedCookie along with custom imports of Rack::Protection::HttpOrigin, and Rack::Cors.

After implementing Omniauth, I had to use the standard enable :sessions inside my Sinatra configure block, as well as set :session_secret with a key. Then I later configured Rack::Session::EncryptedCookie.

configure do
  # added these lines
  enable :sessions
  set :session_secret, ENV.fetch('SESSION_SECRET', SecureRandom.hex(32))
  set :session_length, 3.days
end
...
use Rack::Session::EncryptedCookie,
      key: '_myapp_session',
      expire_after: 60 * 60 * 24 * 30, # 30 days in seconds
      secure: true,
      httponly: Sinatra::Base.environment == :production,
      same_site: :none,
      secret: ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) },
      key_size: 32,
      salt: SecureRandom.hex(32),
      signed_salt: SecureRandom.hex(32)
  use Rack::Protection::SessionHijacking
  use Rack::Protection::RemoteToken

  use Rack::Protection, permitted_origins: [
    ENV.fetch('ALLOWED_URL')
  ], except: %i[http_origin remote_token]

  use Rack::Protection::HttpOrigin,
    allow_if: lambda { |env| env['REQUEST_PATH'].split('/')[1] == 'auth' },
              permitted_origins: [
                ENV.fetch('ALLOWED_URL')
                ]

  use OmniAuth::Builder do
    provider :my_config,
             ENV['OAUTH_CLIENT_ID'],
             ENV['OAUTH_CLIENT_SECRET'],
             origin_param: 'return_url'
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants