Skip to content

Commit

Permalink
Add support for response_type id_token (jjbohn#32)
Browse files Browse the repository at this point in the history
* Add support for response_type id_token

* Simplify the response_type validation

* Remove unnecessary statements from README
  • Loading branch information
januszm authored and m0n9oose committed Aug 9, 2019
1 parent f392719 commit 3f52ccd
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 10 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,18 @@ Configuration details:
**NOTE**: if you use this gem with Devise you should use `:openid_connect` name,
or Devise would route to 'users/auth/:provider' rather than 'users/auth/openid_connect'

* Although `response_type` is an available option, currently, only `:code`
is valid. There are plans to bring in implicit flow and hybrid flow at some
point, but it hasn't come up yet for me. Those flows aren't best practive for
server side web apps anyway and are designed more for native/mobile apps.
* If you want to pass `state` paramete by yourself. You can set Proc Object.
* `response_type` tells the authorization server which grant type the application wants to use,
currently, only `:code` (Authorization Code grant) and `:id_token` (Implicit grant) are valid.
* If you want to pass `state` paramete by yourself. You can set Proc Object.
e.g. `state: Proc.new { SecureRandom.hex(32) }`
* `nonce` is optional. If don't want to pass "nonce" parameter to provider, You should specify
`false` to `send_nonce` option. (default true)
* Support for other client authentication methods. If don't specified
`:client_auth_method` option, automatically set `:basic`.
* Use "OpenID Connect Discovery", You should specify `true` to `discovery` option. (default false)
* In "OpenID Connect Discovery", generally provider should have Webfinger endpoint.
If provider does not have Webfinger endpoint, You can specify "Issuer" to option.
e.g. `issuer: "https://myprovider.com"`
If provider does not have Webfinger endpoint, You can specify "Issuer" to option.
e.g. `issuer: "https://myprovider.com"`
It means to get configuration from "https://myprovider.com/.well-known/openid-configuration".
* The uid is by default using the `sub` value from the `user_info` response,
which in some applications is not the expected value. To avoid such limitations, the uid label can be
Expand Down
1 change: 1 addition & 0 deletions lib/omniauth/openid_connect/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ module OmniAuth
module OpenIDConnect
class Error < RuntimeError; end
class MissingCodeError < Error; end
class MissingIdTokenError < Error; end
end
end
31 changes: 29 additions & 2 deletions lib/omniauth/strategies/openid_connect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ class OpenIDConnect
include OmniAuth::Strategy
extend Forwardable

RESPONSE_TYPE_EXCEPTIONS = {
'id_token' => { exception_class: OmniAuth::OpenIDConnect::MissingIdTokenError, key: :missing_id_token }.freeze,
'code' => { exception_class: OmniAuth::OpenIDConnect::MissingCodeError, key: :missing_code }.freeze,
}.freeze

def_delegator :request, :params

option :name, 'openid_connect'
Expand All @@ -35,7 +40,7 @@ class OpenIDConnect
option :client_jwk_signing_key
option :client_x509_signing_key
option :scope, [:openid]
option :response_type, 'code'
option :response_type, 'code' # ['code', 'id_token']
option :state
option :response_mode # [:query, :fragment, :form_post, :web_message]
option :display, nil # [:page, :popup, :touch, :wap]
Expand Down Expand Up @@ -109,7 +114,7 @@ def callback_phase

raise CallbackError, 'Invalid state parameter' if invalid_state

return fail!(:missing_code, OmniAuth::OpenIDConnect::MissingCodeError.new(params['error'])) unless params['code']
return unless valid_response_type?

options.issuer = issuer if options.issuer.nil? || options.issuer.empty?

Expand All @@ -120,6 +125,9 @@ def callback_phase

discover!
client.redirect_uri = redirect_uri

return id_token_callback_phase if options.response_type.to_s == 'id_token'

client.authorization_code = authorization_code
access_token
super
Expand Down Expand Up @@ -294,6 +302,25 @@ def logout_path_pattern
@logout_path_pattern ||= %r{\A#{Regexp.quote(request_path)}(/logout)}
end

def id_token_callback_phase
user_data = decode_id_token(params['id_token']).raw_attributes
env['omniauth.auth'] = AuthHash.new(
provider: name,
uid: user_data['sub'],
info: { name: user_data['name'], email: user_data['email'] }
)
call_app!
end

def valid_response_type?
return true if params.key?(options.response_type)

error_attrs = RESPONSE_TYPE_EXCEPTIONS[options.response_type]
fail!(error_attrs[:key], error_attrs[:exception_class].new(params['error']))

false
end

class CallbackError < StandardError
attr_accessor :error, :error_reason, :error_uri

Expand Down
15 changes: 14 additions & 1 deletion test/lib/omniauth/strategies/openid_connect_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,20 @@ def test_callback_phase_without_code

strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })

strategy.expects(:fail!)
strategy.expects(:fail!).with(:missing_code, is_a(OmniAuth::OpenIDConnect::MissingCodeError))
strategy.callback_phase
end

def test_callback_phase_without_id_token
state = SecureRandom.hex(16)
nonce = SecureRandom.hex(16)
request.stubs(:params).returns('state' => state)
request.stubs(:path_info).returns('')
strategy.options.response_type = 'id_token'

strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })

strategy.expects(:fail!).with(:missing_id_token, is_a(OmniAuth::OpenIDConnect::MissingIdTokenError))
strategy.callback_phase
end

Expand Down

0 comments on commit 3f52ccd

Please sign in to comment.