stevenmaguire / oauth2-salesforce

Salesforce Provider for the OAuth 2.0 Client
MIT License
31 stars 13 forks source link

Is the Authorization Flow Documented Backwards? #9

Closed txwizard closed 6 years ago

txwizard commented 7 years ago

Following the examples in your README file and the one in the League project page seems to be jumping the gun, since I don't yet have a token. The instructions at https://help.salesforce.com/articleView?id=networks_auth_configure_oauth.htm&type=0 suggest to me that the redirect URL should be https://p6c-dev-ed.my.salesforce.com/services/oauth2/token, rather than https://p6c-dev-ed.my.salesforce.com/services/oauth2/authorize.

The point of failure is at the end of the following IF block.

` if ( !isset ( $_GET [ 'code' ] ) ) { // ---------------------------------------------------------------- // Fetch the authorization URL from the provider; this returns the // urlAuthorize option and generates and applies any necessary // parameters (e.g. state). // ----------------------------------------------------------------

        error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Getting the authorization URL.' ) ;

        //  ------------------------------------------------------------------------------------------
        //  Per https://help.salesforce.com/articleView?id=remoteaccess_oauth_web_sso_flow.htm&type=0,
        //  this should be going to https://login.salesforce.com/services/oauth2/token.
        //  Instead, it's going to https://p6c-dev-ed.my.salesforce.com/services/oauth2/authorize.
        //  ------------------------------------------------------------------------------------------

        $authorizationUrl = $provider->getAuthorizationUrl ( ) ;

        error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Authorization URL per provider     = ' . $authorizationUrl ) ;
        error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': BaseAuthorizationUrlL per provider = ' . $provider->getBaseAuthorizationUrl ( ) ) ;
        error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Domain per provider                = ' . $provider->getDomain ( ) ) ;

        //  ----------------------------------------------------------------
        //  Get the state generated for you and store it to the session.
        //  ----------------------------------------------------------------

        $_SESSION [ 'oauth2state' ] = $provider->getState ( ) ;

        error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': oauth2state = ' . $provider->getState ( ) ) ;

        //  ----------------------------------------------------------------
        //  Redirect the user to the authorization URL.
        //  ----------------------------------------------------------------

        header ( 'Location: ' . $authorizationUrl ) ;
        error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': redirecting to authorization URL.' ) ;
        exit ;
    }   // Check given state against previously stored one to mitigate CSRF attack.

` The above code is in the ShowPageLogin method on my controller.

Attached for reference are the entire controller class and an annotated extract of the Apache error log, where the error_log function left its messages.

I am at a standstill until this is resolved, although I am prepared to make directed modifications to either your provider or the underlying League OAuth library.

ShowPageLogin_20171204_014556.TXT Annotated_Extract_from_Apache_Error_Log_20171204_014928.TXT

stevenmaguire commented 7 years ago

@txwizard I do not understand clearly the problem you are having here. I find it difficult to believe that you need to make direct changes to either of the packages you are consuming. If you feel that is the case, perhaps you could make the changes that you feel will "correct" your issue and open a pull request so I can take a closer look at the actual implementation changes?

txwizard commented 7 years ago

Steve,

Thank you for responding so quickly to my inquiry.

I truly doubt that I need to change anything in either library; I think instead that I am missing a piece of the intended program flow.

The code below is the guts of my login controller, which I constructed by following the Authorization Code Grant example shown in the ReadMe file that accompanies The PHP League OAuth 2 Client package, at https://github.com/thephpleague/oauth2-client. It isn’t at all clear to me what this code expects to find in $_GET [ 'code' ], which is, in my case, NULL.

       $provider = new \Stevenmaguire\OAuth2\Client\Provider\Salesforce (

                   [

                           'clientId'          => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ,

                           'clientSecret'      => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ,

                           'redirectUri'       => 'https://project1.praesidiuminc.com/pch/Menu' ,      // Sample in readme: 'https://example.com/callback-url'

                           'domain'            => 'https://p6c-dev-ed.my.salesforce.com'                // optional, defaults to https://login.salesforce.com

                ] ) ;

       error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': have the SF provider.' ) ;

       error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Base Authorization URL    = ' . $provider->getBaseAuthorizationUrl ( ) ) ;

       error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Domain Name               = ' . $provider->getDomain ( ) ) ;

       if ( !isset ( $_GET [ 'code' ] ) )

       {

         // ----------------------------------------------------------------

         // Fetch the authorization URL from the provider; this returns the

          // urlAuthorize option and generates and applies any necessary

          // parameters (e.g. state).

         // ----------------------------------------------------------------

            error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Getting the authorization URL.' ) ;

            //   ------------------------------------------------------------------------------------------

            //   Per https://help.salesforce.com/articleView?id=remoteaccess_oauth_web_sso_flow.htm&type=0,

            //   this should be going to https://login.salesforce.com/services/oauth2/token.

            //   Instead, it's going to https://p6c-dev-ed.my.salesforce.com/services/oauth2/authorize.

            //   ------------------------------------------------------------------------------------------

          $authorizationUrl = $provider->getAuthorizationUrl ( ) ;

          error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Authorization URL per provider     = ' . $authorizationUrl ) ;

          error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': BaseAuthorizationUrlL per provider = ' . $provider->getBaseAuthorizationUrl ( ) ) ;

          error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Domain per provider                = ' . $provider->getDomain ( ) ) ;

         // ----------------------------------------------------------------

          //      Get the state generated for you and store it to the session.

         // ----------------------------------------------------------------

          $_SESSION [ 'oauth2state' ] = $provider->getState ( ) ;

            error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': oauth2state = ' . $provider->getState ( ) ) ;

         // ----------------------------------------------------------------

          // Redirect the user to the authorization URL.

         // ----------------------------------------------------------------

          header ( 'Location: ' . $authorizationUrl ) ;

            error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': redirecting to authorization URL.' ) ;

          exit ;

       }    // Check given state against previously stored one to mitigate CSRF attack.

       elseif ( empty ( $_GET [ 'state' ] ) || ( isset ( $_SESSION [ 'oauth2state' ] ) && $_GET [ 'state' ] !== $_SESSION [ 'oauth2state' ] ) )

       {

            error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': entering the ElseIf block.' ) ;

           if ( isset ( $_SESSION [ 'oauth2state' ] ) )

           {

                unset ( $_SESSION [ 'oauth2state' ] );

           } // if ( isset ( $_SESSION [ 'oauth2state' ] ) )

           exit ( 'Invalid state' ) ;

       }    // TRUE block) if ( !isset ( $_GET [ 'code' ] ) ) AND elseif ( empty ( $_GET [ 'state' ] ) || ( isset ( $_SESSION [ 'oauth2state' ] ) && $_GET [ 'state' ] !== $_SESSION [ 'oauth2state' ] ) )

       else

       {

           try

           {

               // ------------------------------------------------------------

               // Try to get an access token using the authorization code

               // grant.

               // ------------------------------------------------------------

                 error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Getting authorization with code = ' . $_GET [ 'code' ] ) ;

               $accessToken = $provider->getAccessToken ( 'authorization_code' ,

                                                          [ 'code' => $_GET [ 'code' ] ] ) ;

               // ------------------------------------------------------------

               // We have an access token, which we may use in authenticated

               // requests against the service provider's API.

               // ------------------------------------------------------------

               echo 'Access Token: ' . $accessToken->getToken ( ) . "<br>";

               echo 'Refresh Token: ' . $accessToken->getRefreshToken ( ) . "<br>";

               echo 'Expired in: ' . $accessToken->getExpires ( ) . "<br>";

               echo 'Already expired? ' . ( $accessToken->hasExpired ( ) ? 'expired' : 'not expired') . "<br>";

               error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Access Token:    ' . $accessToken->getToken ( ) ) ;

               error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Refresh Token:   ' . $accessToken->getRefreshToken ( ) ) ;

               error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Expires in:      ' . $accessToken->getExpires ( ) ) ;

               error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Already expired? ' . ( $accessToken->hasExpired ( ) ? 'expired' : 'not expired' ) ) ;

               // ------------------------------------------------------------

               //      Using the access token, we may look up details about the

               //      resource owner.

               // ------------------------------------------------------------

               $resourceOwner = $provider->getResourceOwner ( $accessToken ) ;

               var_export ( $resourceOwner->toArray ( ) ) ;

               // ------------------------------------------------------------

               // The provider provides a way to get an authenticated API

               // request for the service, using the access token; it returns

               // an object conforming to Psr\Http\Message\RequestInterface.

               // ------------------------------------------------------------

               error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Using access token to request authorization.' ) ;

               // ---------------------------------------------------------------------

               // His example was: 'http://brentertainment.com/oauth2/lockdin/resource'

               // ---------------------------------------------------------------------

               $request = $provider->getAuthenticatedRequest (

                                                               'GET',

                                                               'https://p6c-dev-ed.my.salesforce.com/idp/login?app=0sp1I000000CaT8' ,

                                                               $accessToken

                                                             ) ;

           } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e)

           { // Failed to get the access token or user details.

                 error_log ( 'At line ' . __LINE__ . ' in ' . __METHOD__ . ': Exception caught: Message = ' . $e->getMessage ( ) ) ;

               exit ( $e->getMessage ( ) ) ;

           }

       }    // FALSE block, if ( !isset ( $_GET [ 'code' ] ) ) AND elseif ( empty ( $_GET [ 'state' ] ) || ( isset ( $_SESSION [ 'oauth2state' ] ) && $_GET [ 'state' ] !== $_SESSION [ 'oauth2state' ] ) )

As I was preparing for sleep, shortly after I opened this issue, it occurred to me that the missing piece might be a separate request for the access token, which is supposed to somehow find its way into the $_GET array. That would certainly be consistent with my previous experience with Web APIs that are secured by a cryptographic token.

I would love to close this issue, and open a new issue with The PHP League, in support of a proposal to incorporate a clearer example in its ReadMe file.

Please bear in mind that I am new to soliciting support for open source libraries, and to PHP in general. The majority of my background is in Microsoft Visual C# and Visual C++. The most similar dynamic language with which I have any experience is classic Perl 5. I am all ears when it comes to suggestions for getting support, etc.

In that spirit, I look forward to hearing further from you soon.

Respectfully yours,

David A. Gray

Email: mailto:dgray@wizardwrx.com dgray@wizardwrx.com

Cell: +1 (817) 298-0867

WW_Logo_Color_113x83

From: Steven Maguire [mailto:notifications@github.com] Sent: Monday, December 04 2017 09:31 To: stevenmaguire/oauth2-salesforce Cc: David A. Gray; Mention Subject: Re: [stevenmaguire/oauth2-salesforce] Is the Authorization Flow Documented Backwards? (#9)

@txwizard https://github.com/txwizard I do not understand clearly the problem you are having here. I find it difficult to believe that you need to make direct changes to either of the packages you are consuming. If you feel that is the case, perhaps you could make the changes that you feel will "correct" your issue and open a pull request so I can take a closer look at the actual implementation changes?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/stevenmaguire/oauth2-salesforce/issues/9#issuecomment-348996857 , or mute the thread https://github.com/notifications/unsubscribe-auth/AS4DQWVtZoUbZrPOdr9GEOvWTWbBxxUYks5s9BAxgaJpZM4Q0Qiv . https://github.com/notifications/beacon/AS4DQbC9JVluYne0xrBeA984W7CxTkcZks5s9BAxgaJpZM4Q0Qiv.gif

txwizard commented 6 years ago

I've done some more digging, including spelunking in the demonstration application mentioned in the League ReadMe file. Doing so uncovered a couple of things that I needed to change, including addition of a header ( 'response_type: code' ) to the top of the method, to guarantee that it always gets included. I also reconfigured the application to expect a postback to the login page.

Attached to this message are the following items.

  1. ShowPageLogin_20171205_143054.TXT is the updated ShowPageLogin method from my controller class.
  2. Formatted_Error_Logs_20171205_142200.TXT is a lightly reformatted segment from the Apache Web server error_log file that I copied down from the Amazon EC2 instance on which the Web site is installed. URL decoding was provided by way of the Web form displayed at https://www.urldecoder.org/.
  3. Login_Error_AWS_20171205_132639.PDF is a printed version of the response returned by the Salesforce login endpoint when I attempted to log in by exiting, at line 67 in the attached excerpt. This corresponds the Apache error log message that begins, "At line 223." RFC 6749 lists and defines a number of error messages, none of which appears to match the message shown herein, which is clearly coming from the Salesforce IDP, as can be verified by examining the URL shown in the lower left corner of the page.
  4. My_Praesidium_Central_Hub_SF_Configuration_20171205_120049.PDF is a printout of my connected application settings page in my Salesforce development org.

I am completely at a loss to understand why the Salesforce IDP thinks that my redirect URI is mismatched with the URI that is registered with Salesforce.

I look forward to your input, preferably sooner than later, as this project is now stalled because of this issue. Hailing frequencies are wide open.

ShowPageLogin_20171205_143054.TXT Formatted_Error_Logs_20171205_142200.TXT Login_Error_AWS_20171205_132639.PDF My_Praesidium_Central_Hub_SF_Configuration_20171205_120049.PDF

txwizard commented 6 years ago

At long last, after much digging, during which I learned a great deal about both PHP and the Apache mod_rewrite extension, I resolved the issue by adding the QSA flag to the RewriteRule that was truncating the querystring.