diff --git a/lib/auth0/api/authentication_endpoints.rb b/lib/auth0/api/authentication_endpoints.rb index b01178147..7116b2c13 100644 --- a/lib/auth0/api/authentication_endpoints.rb +++ b/lib/auth0/api/authentication_endpoints.rb @@ -48,12 +48,16 @@ def api_token( # @param client_id [string] Client ID for the application # @param client_secret [string] Client secret for the application. Ignored if using Client Assertion # Required only if it was set at the GET /authorize endpoint + # @param code_verifier [string] Cryptographically random key used to generate the + # code_challenge passed to GET /authorize. Required for the Authorization Code Flow + # with PKCE. # @return [Auth0::AccessToken] Returns the access_token and id_token def exchange_auth_code_for_tokens( code, redirect_uri: nil, client_id: @client_id, - client_secret: @client_secret + client_secret: @client_secret, + code_verifier: nil ) raise Auth0::InvalidParameter, 'Must provide an authorization code' if code.to_s.empty? @@ -63,6 +67,7 @@ def exchange_auth_code_for_tokens( code: code, redirect_uri: redirect_uri } + request_params[:code_verifier] = code_verifier unless code_verifier.nil? populate_client_assertion_or_secret(request_params, client_id: client_id, client_secret: client_secret) diff --git a/test/unit/authentication_endpoints_test.rb b/test/unit/authentication_endpoints_test.rb index decf86386..7f184c5f0 100644 --- a/test/unit/authentication_endpoints_test.rb +++ b/test/unit/authentication_endpoints_test.rb @@ -177,6 +177,46 @@ def test_exchange_auth_code_for_tokens_with_client_assertion refute_nil result.access_token end + def test_exchange_auth_code_for_tokens_with_code_verifier + stub_request(:post, "https://#{@domain}/oauth/token") + .with do |req| + body = JSON.parse(req.body, symbolize_names: true) + body[:grant_type] == "authorization_code" && + body[:code] == "the_auth_code" && + body[:code_verifier] == "the_code_verifier" + end + .to_return( + status: 200, + body: { "id_token" => "id_token", "access_token" => "test_access_token", "expires_in" => 86_400 }.to_json, + headers: { "Content-Type" => "application/json" } + ) + + result = @client_secret_instance.send( + :exchange_auth_code_for_tokens, "the_auth_code", code_verifier: "the_code_verifier" + ) + + assert_kind_of Auth0::AccessToken, result + refute_nil result.access_token + end + + def test_exchange_auth_code_for_tokens_omits_code_verifier_when_not_provided + stub_request(:post, "https://#{@domain}/oauth/token") + .with do |req| + body = JSON.parse(req.body, symbolize_names: true) + !body.key?(:code_verifier) + end + .to_return( + status: 200, + body: { "id_token" => "id_token", "access_token" => "test_access_token", "expires_in" => 86_400 }.to_json, + headers: { "Content-Type" => "application/json" } + ) + + result = @client_secret_instance.send(:exchange_auth_code_for_tokens, "the_auth_code") + + assert_kind_of Auth0::AccessToken, result + refute_nil result.access_token + end + # --- exchange_refresh_token --- def test_exchange_refresh_token_with_client_secret