@@ -24,11 +24,22 @@ def self.inherited(subclass)
2424 option :client_secret , nil
2525 option :client_options , { }
2626 option :authorize_params , { }
27- option :authorize_options , [ : scope, : state]
27+ option :authorize_options , %i[ scope state ]
2828 option :token_params , { }
2929 option :token_options , [ ]
3030 option :auth_token_params , { }
3131 option :provider_ignores_state , false
32+ option :pkce , false
33+ option :pkce_verifier , nil
34+ option :pkce_options , {
35+ :code_challenge => proc { |verifier |
36+ Base64 . urlsafe_encode64 (
37+ Digest ::SHA2 . digest ( verifier ) ,
38+ :padding => false ,
39+ )
40+ } ,
41+ :code_challenge_method => "S256" ,
42+ }
3243
3344 attr_accessor :access_token
3445
@@ -48,22 +59,29 @@ def request_phase
4859 redirect client . auth_code . authorize_url ( { :redirect_uri => callback_url } . merge ( authorize_params ) )
4960 end
5061
51- def authorize_params
62+ def authorize_params # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
5263 options . authorize_params [ :state ] = SecureRandom . hex ( 24 )
53- params = options . authorize_params . merge ( options_for ( "authorize" ) )
64+
5465 if OmniAuth . config . test_mode
5566 @env ||= { }
5667 @env [ "rack.session" ] ||= { }
5768 end
69+
70+ params = options . authorize_params
71+ . merge ( options_for ( "authorize" ) )
72+ . merge ( pkce_authorize_params )
73+
74+ session [ "omniauth.pkce.verifier" ] = options . pkce_verifier if options . pkce
5875 session [ "omniauth.state" ] = params [ :state ]
76+
5977 params
6078 end
6179
6280 def token_params
63- options . token_params . merge ( options_for ( "token" ) )
81+ options . token_params . merge ( options_for ( "token" ) ) . merge ( pkce_token_params )
6482 end
6583
66- def callback_phase # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity
84+ def callback_phase # rubocop:disable Metrics/ AbcSize, Metrics/ CyclomaticComplexity, Metrics/ MethodLength, Metrics/ PerceivedComplexity
6785 error = request . params [ "error_reason" ] || request . params [ "error" ]
6886 if error
6987 fail! ( error , CallbackError . new ( request . params [ "error" ] , request . params [ "error_description" ] || request . params [ "error_reason" ] , request . params [ "error_uri" ] ) )
@@ -84,27 +102,44 @@ def callback_phase # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength
84102
85103 protected
86104
105+ def pkce_authorize_params
106+ return { } unless options . pkce
107+
108+ options . pkce_verifier = SecureRandom . hex ( 64 )
109+
110+ # NOTE: see https://tools.ietf.org/html/rfc7636#appendix-A
111+ {
112+ :code_challenge => options . pkce_options [ :code_challenge ]
113+ . call ( options . pkce_verifier ) ,
114+ :code_challenge_method => options . pkce_options [ :code_challenge_method ] ,
115+ }
116+ end
117+
118+ def pkce_token_params
119+ return { } unless options . pkce
120+
121+ { :code_verifier => session . delete ( "omniauth.pkce.verifier" ) }
122+ end
123+
87124 def build_access_token
88125 verifier = request . params [ "code" ]
89126 client . auth_code . get_token ( verifier , { :redirect_uri => callback_url } . merge ( token_params . to_hash ( :symbolize_keys => true ) ) , deep_symbolize ( options . auth_token_params ) )
90127 end
91128
92129 def deep_symbolize ( options )
93- hash = { }
94- options . each do |key , value |
130+ options . each_with_object ( { } ) do |( key , value ) , hash |
95131 hash [ key . to_sym ] = value . is_a? ( Hash ) ? deep_symbolize ( value ) : value
96132 end
97- hash
98133 end
99134
100135 def options_for ( option )
101136 hash = { }
102137 options . send ( :"#{ option } _options" ) . select { |key | options [ key ] } . each do |key |
103138 hash [ key . to_sym ] = if options [ key ] . respond_to? ( :call )
104- options [ key ] . call ( env )
105- else
106- options [ key ]
107- end
139+ options [ key ] . call ( env )
140+ else
141+ options [ key ]
142+ end
108143 end
109144 hash
110145 end
0 commit comments