Closed helgatheviking closed 4 years ago
I'm still stuck on authentication and could use some help.
try {
/*
* Get oauth token.
*/
// client credentials grant
$config = [
'auth' => [
'type' => 'client_credentials',
'appId' => $appId,
'appSecret' => $appSecret
]
];
$client = ApiClientFactory::createClient($config);
var_dump($client->getAuthenticator()->getTokens());
} catch (\HelpScout\Api\Exception\ValidationErrorException $e) {
$error = $e->getError();
var_dump(
// A reference id for that request. Including this anytime you contact Help Scout will
// empower us to dig right to the heart of the issue
$error->getCorrelationId(),
// Details about the invalid fields in the request
$error->getErrors()
);
exit;
}
The var_dump
returns an array with null values.
array(4) { ["refresh_token"]=> NULL ["token_type"]=> string(6) "Bearer" ["access_token"]=> NULL ["expires_in"]=> NULL }
The app ID and secret are defined as the same values from this screen:
And when I use curl on the command line...
curl -X POST https://api.helpscout.net/v2/oauth2/token \
--data "grant_type=client_credentials" \
--data "client_id={application_id}" \
--data "client_secret={application_secret}"
I get a token response so the credentials seem correct. What else could be causing me to not get a token with the PHP wrappers? I don't get an error, I just don't get anything.
FYI, I am using v2 of the php API.
Hey Kathy,
You're right, our auth examples need to be revisited! You can actually use the client credentials and the access token will be fetched for you and you won't need to worry about the redirect url at all. After you create your OAuth2 app you can use the id/secret directly with the client.
I'll submit a PR with some changes to the docs and examples. In the meantime, try switching to use the Client Credentials like they are in this example and let me know how it goes!
Following up on this issue how do you know when a token is expired and needs to be refreshed?
In SDK v3 you can catch a HelpScout\Api\Exception\AuthenticationException
and handle refresh the token in the handling for that exception and retry.
For SDK v2, an exception will be thrown and looking for the 401 status code will indicate there's an auth issue and the token needs to be refreshed:
use GuzzleHttp\Exception\ClientException;
try {
// make calls to the API
} catch (ClientException $e) {
if ($e->getResponse()->getStatusCode() == '401') {
// token needs to be refreshed
}
}
Hi @bkuhl thanks for following up with me.
So using the client credentials like this:
$client = ApiClientFactory::createClient();
$client = $client->useClientCredentials($appId, $appSecret);
I'm able to authenticate and get/put info to my mailbox! So that's better than this morning... well I hadn't even tried interacting with the API since I was stuck thinking I needed tokens.
Using this approach do you not need to worry about storing a token at all? If you do still need a token, how does one get a token from the client?
Sure thing. Using the client credentials like that, an access token is fetched for you (you can see the details on the RESTful call that's made in the API docs), so it's a bit of a shortcut. An access token is still used under the covers to interact with the API. This means it can still expire and can still require a token to be refreshed.
Digging into this deeper, it looks like a refresh token is invalidated when a new one is issued. So you'll want to ensure the same token is used for all api interaction until it expires as issuing a new one will force previously issued ones to expire.
Thanks @bkuhl . When I authenticate on the command line (with the curl command shown in the docs) I get the token in response. But I'm still not sure how to even get the the tokens via the PHP API. For me, $client->getTokens()
and $client->getAuthenticator()->getTokens()
returns an array of null values so there's nothing for me to save or reuse the next time. So currently I have no choice but to keep re-authenticating.
And on the subject of tokens, I think the docs should include the entire auth/re-auth flow. I think I'm understanding it to be as follows, but an example would go a long way.
I agree, the docs can be improved here as well. I'll give the docs a review. In the meantime, https://github.com/helpscout/helpscout-api-php#refreshing-expired-tokens contains the missing piece here. The access token is lazily fetched so that it's only fetched if needed, rather than when the client is configured with a token/secret. So to get the access token, you'd do this:
$client = ApiClientFactory::createClient();
$client = $client->useClientCredentials($appId, $appSecret);
$accessToken = $client->getAuthenticator()->fetchAccessAndRefreshToken()->accessToken();
And on the subject of tokens, I think the docs should include the entire auth/re-auth flow. I think I'm understanding it to be as follows, but an example would go a long way.
Sure, here's an example and some thoughts around this.
$client = ApiClientFactory::createClient();
$client = $client->useClientCredentials($appId, $appSecret);
/**
* This example shows how to catch an Auth exception, refresh the token and retry the same work again.
*/
function autoRefreshToken(ApiClient $client, Closure $closure) {
$attempts = 0;
do {
try {
return $closure($client);
} catch (\HelpScout\Api\Exception\AuthenticationException $e) {
$client->getAuthenticator()->fetchAccessAndRefreshToken();
}
$attempts++;
} while($attempts < 1);
throw new RuntimeException('Authentication failure loop encountered');
}
$users = autoRefreshToken($client, function (ApiClient $client) {
return $client->users()->list();
});
print_r($users->getFirstPage()->toArray());
Ideally, the SDK internally would implement the ability to refresh automatically via a Guzzle middleware that would enable the automatic renewal of a refresh token when expired, then automatically retry the original request.
This is awesome, thank you! I think the part about "lazy" loading the token was a big missing piece for me.
You're welcome! I'm glad this is helpful!
@bkuhl Sorry to be back... but there's a new piece I'm stuck on. In the autofresh example you've created, where would you get the tokens to store in my app. And how would you use them the next time? My app will be processing a webhook, so doing it the current way means I am getting a token every time a webhook is delivered, or no?
No problem, that's what we're here for!
In the autofresh example you've created, where would you get the tokens to store in my app
In the catch clause here, you could use $client->getAuthenticator()->fetchAccessAndRefreshToken()->accessToken();
to obtain the access token and do whatever you need to store it within your app. $client
in the below example is updated with the new token and will use it.
try {
return $closure($client);
} catch (\HelpScout\Api\Exception\AuthenticationException $e) {
// This will automatically configure $client to use the new token
$client->getAuthenticator()->fetchAccessAndRefreshToken();
}
how would you use them the next time?
In this documentation you can see a few different ways to configure the client to use a refresh token you already have.
so doing it the current way means I am getting a token every time a webhook is delivered, or no?
I think you'll want to store it and only refresh when necessary. If you have many webhooks hitting your app and one process obtains a new refresh token, the other processes may fail because they aren't using the new token. For this reason, obtaining a new token on each webhook wouldn't scale very well.
Awesome... I appreciate it. And good news is I think I have fetched token! lol. now, just to see how the code behaves when the token expires.
The auth example seems like it's missing some newbie details.
$client->getTokens()
) is returning an array with all keys empty, so I'm stuck at the moment.Thanks!