FuelSDK-PHP icon indicating copy to clipboard operation
FuelSDK-PHP copied to clipboard

OAuth 2.0 support

Open danniehansen opened this issue 5 years ago • 23 comments

Hi,

Looks like SalesForce Marketing Cloud soon defaults to OAuth 2.0 without the possibility of using legacy authentication https://jmp.sh/tcbTj8c

Is there any plan to support packages created using OAuth 2.0? Currently getting: https://jmp.sh/5jdmXSk & is unable to find any documentation that would allow me to use OAuth 2.0

danniehansen avatar Apr 15 '19 12:04 danniehansen

I second that. This SDK will be deprecated in starting August of this year. Although legacy apps will still work. Any new apps created will not work with this SDK starting in August. Anyone looking into this? We just started using Salesforce Marketing Cloud a few days ago.

adjc98 avatar Apr 16 '19 20:04 adjc98

with a few tweaks i was able to fetch the access token in the sdk. REST is working, however my token is not accepted for SOAP: SOAP Authorization failed for Oauth token: XXXXXXXXXX. Reason: ParseError.

i wonder if the problem is that an access token is nu the same as an oauth token (should be right?), or something else is wrong.. i can make a pull request if i get the connection working. Any indicators are welcome

jonathandezoete avatar Apr 19 '19 09:04 jonathandezoete

the following xml envelope is created:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
                   xmlns:ns1="http://exacttarget.com/wsdl/partnerAPI">
    <SOAP-ENV:Header>
        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                       SOAP-ENV:mustUnderstand="1">
            <wsse:UsernameToken>
                <wsse:Username>*</wsse:Username>
                <wsse:Password
                        Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
                    *
                </wsse:Password>
                <wsse:Nonce>R8Wv/0QcgbmTyVVNdMCpYPLWsoTWqfUzfLT+IemzB64=</wsse:Nonce>
                <wsu:Created
                        xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                    2019-04-19T09:05:32Z
                </wsu:Created>
            </wsse:UsernameToken>
        </wsse:Security>
        <oAuth xmlns="http://exacttarget.com">
            <oAuthToken>
                XXXX
            </oAuthToken>
        </oAuth>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body>
        <ns1:DefinitionRequestMsg>
            <ns1:DescribeRequests>
                <ns1:ObjectDefinitionRequest>
                    <ns1:ObjectType>List</ns1:ObjectType>
                </ns1:ObjectDefinitionRequest>
            </ns1:DescribeRequests>
        </ns1:DefinitionRequestMsg>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

jonathandezoete avatar Apr 19 '19 09:04 jonathandezoete

Same here, I am getting the OAuth 2.0 token no problem and Rest is working fine. I am still trying to get it to work with Soap. I am using the Client ID and Client Secret from an installed pacakage instead of a username and password.

adjc98 avatar Apr 22 '19 14:04 adjc98

I'm in the same boat. Hopefully there's a solution. I need to read/write to a data extension.

will-ashworth avatar Apr 23 '19 05:04 will-ashworth

Got a successful login with this envelope (make sure the request goes to soap endpoint):

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
                   xmlns:ns1="http://exacttarget.com/wsdl/partnerAPI">
<SOAP-ENV:Header>
    <fueloauth xmlns="http://exacttarget.com">
        XXXXXXXX
    </fueloauth>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
    <ns1:DefinitionRequestMsg>
        <ns1:DescribeRequests>
            <ns1:ObjectDefinitionRequest>
                <ns1:ObjectType>List</ns1:ObjectType>
            </ns1:ObjectDefinitionRequest>
        </ns1:DescribeRequests>
    </ns1:DefinitionRequestMsg>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

now lets see if i can cleanly override the client.

jonathandezoete avatar Apr 23 '19 09:04 jonathandezoete

Thank you @jonathandezoete

Any idea if there are any examples of making the requests the newer way in Postman or PHP? Right now, the SDK is dead to me without proper authentication (unless I'm missing something obvious), and I'm willing to custom code something, but having a hard time locating examples of a working authentication scheme for the OAuth 2.0 stuff in Postman while testing queries against the endpoints.

We're doing everything through Marketing Cloud (formerly Exact Target), so I think I'm asking this in the right place.

will-ashworth avatar Apr 23 '19 21:04 will-ashworth

Hi @will-ashworth

You can use the SDK no problem, just change the client. Havent had the time to do a clean fork, but if you substitute the ET_Client with the attached client php file. Then you can make a SOAP call like this:

$client = new SQZL_ET_Client(true, false,
    [
        "baseUrl" => '..',
        "baseAuthUrl" => '..',
        'baseSoapUrl' => '..',
        'appsignature' => 'none',
        "clientid" => '..',
        "clientsecret" => '..',
        "sdl" => 0
    ]);
$client->setInternalAuthToken('', "**ACCESS_TOKEN**");

$getList = new ET_List();
$getList->authStub = $client;
$getResponse = $getList->get();

SQZL_ET_Client.zip

For REST calls i just use guzzle.

jonathandezoete avatar Apr 23 '19 21:04 jonathandezoete

@jonathandezoete this is pretty cool, how much of the ET_Client did you have to modify?

garek007 avatar Apr 24 '19 17:04 garek007

Not so much. See for yourself: https://www.diffnow.com/report/38kdi

jonathandezoete avatar Apr 24 '19 19:04 jonathandezoete

@jonathandezoete I dropped this file into the src folder, but I don't think it's autoloading. I see that your namespace is different. Is this meant to be put into another directory? Update. I ended up changing the namespace to FuelSdk since it's dropped in the same folder. You have a different class name so no need to create a new namspace. I'm guessing when you posted that it's plucked straight out of a project you're working on. Now I'm getting a new error.

Undefined index: tenantKey in C:\wamp64\www\fuel-project\vendor\salesforce-mc\fuel-sdk-php\src\SQZL_ET_Client.php on line 75

Ok I got that fixed by adding a parameter to the function call and just set tenantKey to "whatever". I'm not sure what that's used for. So I also wasn't sure where to set the default WSDL save location so I set the first parameter to false. I had to change line 79 to use my specific wsdl endpoint with the specific stack so

https://webservice.s7.exacttarget.com/etframework.wsdl

So it seems that things are getting passed without failure, but I still don't think I'm connecting. I do a var_dump of $client and I don't see anything like an access token in there. I also don't know what to do with the next line

$client->setInternalAuthToken('', "ACCESS_TOKEN");

garek007 avatar Apr 25 '19 19:04 garek007

@garek007 correct, didnt do anything besides copy it ;) The error you see is because the sdk has "== null" statements rather than a "!isset". I don't know why. I worked around that by setting the $this->tenantKey = $params['tenantKey']; in the changed class constructor. you can also just update the statements.

jonathandezoete avatar Apr 25 '19 20:04 jonathandezoete

the wdsl is fine. you should first get your access token and put that with the setInternalAuthToken(). you can get it with guzzle. try something like this:

                $api = new Client();
                $result = $api->request('POST', $auth_url . '/v2/token' , ['json' => ['client_id' => $clientId, 'client_secret' => $clientSecret, 'grant_type' => "client_credentials"]]);
                $tokenInfo = json_decode($result->getBody()->getContents(), true);
                $this->currentAccessToken = $tokenInfo['access_token'];

jonathandezoete avatar Apr 25 '19 20:04 jonathandezoete

Thanks @jonathandezoete I think I see what you're doing here. A few more questions and some things I don't understand.

If I am using Guzzle and passing client_id and client_secret to the auth URL and then getting a token that I can then use in client, what would I still need to make the new SQZL_ET_Client call? I understand that I need to construct the object but I guess I'm wondering why we also need to pass the credentials to it as well? Seems like we're already doing it once with Guzzle.

Secondly, I've been just instantiating a new object all over the place. I know this is not recommended, but I'm not sure how to simply reuse the access token for subsequent calls.

garek007 avatar Apr 25 '19 21:04 garek007

Ok so I was able to connect using the code below and the getList request worked.

<?php
require __DIR__ . '/vendor/autoload.php';
use FuelSdk\ET_Client;
use FuelSdk\SQZL_ET_Client;
use FuelSdk\ET_List;
use GuzzleHttp\Client;

//Next, create an instance of the ET_Client class:
$auth_url = 'https://mcvszr8q726sp9j-0n98r70tppp1.auth.marketingcloudapis.com/';
$clientId = 'shhhhSecret';
$clientSecret = 'shhhhSecret';

$api = new Client();
$result = $api->request('POST', $auth_url . '/v2/token' , ['json' => ['client_id' => $clientId, 'client_secret' => $clientSecret, 'grant_type' => "client_credentials"]]);
$tokenInfo = json_decode($result->getBody()->getContents(), true);


$myclient = new SQZL_ET_Client(false, false,
  [
      "baseUrl" => 'https://mcvszr8q726sp9j-0n98r70tppp1.auth.marketingcloudapis.com/',
      "baseAuthUrl" => 'https://mcvszr8q726sp9j-0n98r70tppp1.auth.marketingcloudapis.com/',
      'baseSoapUrl' => 'https://mcvszr8q726sp9j-0n98r70tppp1.soap.marketingcloudapis.com/',
      'appsignature' => 'none',
      "clientid" => 'shhhhSecret',
      "clientsecret" => 'shhhhSecret',
      "sdl" => 0
  ]);


$myclient->setInternalAuthToken('', $tokenInfo['access_token']);


$getList = new ET_List();

$getList->authStub = $myclient;

$getResponse = $getList->get();

var_dump($getResponse);

This is awesome @jonathandezoete thanks for sending this. The thing I still don't get, is why am I getting this error

Notice: Undefined index: tenantKey in C:\wamp64\www\fuel-project\vendor\salesforce-mc\fuel-sdk-php\src\SQZL_ET_Client.php on line 75

and how do I persist my authorization for subsequent calls (ie page to page) without instantiating a new object each time?

garek007 avatar Apr 25 '19 21:04 garek007

My colleague tidied things up a bit:

ET_Client.zip

@garek007 Thats probably because you are using a different tenantkey than is used in the constructor (the previously mentioned == null comparison). For saving the token you can just reuse the $myclient. for saving the token you can put it in a local var, use your own caching mechanism or implement the ET_CacheService from the original client.

jonathandezoete avatar Apr 27 '19 08:04 jonathandezoete

Thanks @jonathandezoete !

garek007 avatar Apr 28 '19 18:04 garek007

Thanks a lot @jonathandezoete and @garek007 !

But now I have this issue when I try to get list : $getResponse = $getList->get();

Could not resolve host: Service.asmx

Do you have the same error ?

pfortin-expertime avatar May 14 '19 02:05 pfortin-expertime

Update: the baseSoapUrl wasn't correct

pfortin-expertime avatar May 14 '19 03:05 pfortin-expertime

Hi guys,

I've created a fork here (please use v1.2.4) based on the branch Oauth2Implementation thanks to the work of @sfcbetiuc

All you have to do is pass a new parameter "useOAuth2Authentication" and set true as value.

        $params = [
            "clientid" => XXXX,
            "clientsecret" => XXXXX,
            'appsignature' => 'none',
            "sdl" => 0,
            "xmlloc" => $xmlloc,
            "tenantKey" => "yourwhateverkey",
            "useOAuth2Authentication" => 'true'
        ];

        $client = new ET_Client(false, false, $params);

pfortin-expertime avatar May 16 '19 07:05 pfortin-expertime

this is awesome! thanks @pfortin-expertime

garek007 avatar May 16 '19 11:05 garek007

That's awesome! Anyway we can get it merged into this repository?

adjc98 avatar May 16 '19 12:05 adjc98

3 years later and I find this. I'm doing well with the api, but looking at ET_Client.php and I can't figure out for the life of me what tenantKey is supposed to be. I've tried it with my subdomain, MID, nothing works. Looking at ET_Client.php I don't even see how/where it is set.

Regarding the if ($this->tenantTokens[$tenantKey] == null) statements, I did change at least one to isset() in my forked repo.

garek007 avatar Dec 29 '22 18:12 garek007