oauth2-client
oauth2-client copied to clipboard
Help with custom provider integration for Daylite API
Hi,
I am trying to create a custom provider for Daylite API and I can't figure it out. I've looked at Daylite's authentication instructions & examples of existing providers, but I still can't figure out what I need to customise.
Can someone please help me through it? Are there any detailed step-by-step custom provider integration instructions that I can refer to?
Regards, Alex
any updates?
This is how I did it. In summary, you need to:
- Add your own Provider files under
/vendor/league/oauth2-client/src/Provider. - Autoload the
oauth2-clientlibrary. - Go through authorization steps with Daylite API to get your
access_token/refresh_tokenpair. - Use Daylite API, while the
access_tokenis fresh. - When the
access_tokenexpires, refresh it using theoauth2-clientprovider again.
All of this was done a few years ago, so not sure what has changed since. This may not work 100% as is, but it will be a good start for you.
Custom Daylite provider files
Daylite.php
<?php
namespace YOUR_NAMESPACE_BASE\OAuth2\Client\Provider;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
/**
* Represents a Daylite service provider that may be used to interact with Daylite API.
*/
class Daylite extends AbstractProvider
{
use BearerAuthorizationTrait;
/**
* @var string Key used in the access token response to identify the resource owner.
*/
const ACCESS_TOKEN_RESOURCE_OWNER_ID = 'Daylite';
/**
* @var string
*/
protected $baseAPIOauthUrl = 'https://www.marketcircle.com/account/oauth/'; // authorize|token
protected $baseApiUrl = 'https://api.marketcircle.net'; // data
/**
* Get authorization url to begin OAuth flow
*
* @return string
*/
public function getBaseAuthorizationUrl()
{
return $this->baseAPIOauthUrl . 'authorize';
}
/**
* Get access token url to retrieve token
*
* @param array $params
*
* @return string
*/
public function getBaseAccessTokenUrl(array $params)
{
return $this->baseAPIOauthUrl . 'token';
}
/**
* Get access token url to retrieve token
*
* @param array $params
*
* @return string
*/
public function getBaseAPIUrl()
{
return $this->baseApiUrl;
}
/**
* Get provider url to fetch user details
*
* @param AccessToken $token
*
* @return string
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
return $this->baseApiUrl . 'info';
}
/**
* Get the default scopes used by this provider.
*
* This should not be a complete list of all scopes, but the minimum
* required for the provider user interface!
*
* @return array
*/
public function getDefaultScopes()
{
return [ 'daylite:read' ];
}
/**
* Check a provider response for errors.
*
* @throws IdentityProviderException
* @param ResponseInterface $response
* @param string $data Parsed response data
* @return void
*/
protected function checkResponse(ResponseInterface $response, $data)
{
$statusCode = $response->getStatusCode();
if ($statusCode >= 400) {
throw new IdentityProviderException(
isset($data['error']) ? $data['error'] : $response->getReasonPhrase(),
$statusCode,
$response
);
}
}
/**
* Generate a user object from a successful user details request.
*
* @param object $response
* @param AccessToken $token
* @return DayliteResourceOwner
*/
protected function createResourceOwner(array $response, AccessToken $token)
{
return new DayliteResourceOwner($response);
}
}
DayliteResourceOwner.php
<?php
namespace YOUR_NAMESPACE_BASE\OAuth2\Client\Provider;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
use League\OAuth2\Client\Tool\ArrayAccessorTrait;
/**
* Represents a Daylite resource owner for use with the Daylite provider.
*/
class DayliteResourceOwner implements ResourceOwnerInterface
{
use ArrayAccessorTrait;
/**
* Raw response
*
* @var array
*/
protected $response;
/**
* Creates new resource owner.
*
* @param array $response
*/
public function __construct(array $response = array())
{
$this->response = $response;
}
/**
* Get resource owner id
*
* @return string
*/
public function getId()
{
return end(explode('/', $this->getResponseData('user')));
}
/**
* Get resource owner First Name
*
* @return string
*/
public function getFirstName()
{
return $this->getResponseData('first_name');
}
/**
* Get resource owner Last Name
*
* @return string
*/
public function getLastName()
{
return $this->getResponseData('last_name');
}
/**
* Get resource owner Full Name
*
* @return string
*/
public function getName()
{
return ( $this->getFirstName() . ' ' . $this->getLastName() );
}
/**
* Get Client ID (aka Application Identifier)
*
* @return string
*/
public function getClientID()
{
return $this->getApplicationIdentifier();
}
/**
* Get Application Identifier (aka Client ID)
*
* @return string
*/
public function getApplicationIdentifier()
{
return $this->getResponseData('application_identifier');
}
/**
* Attempts to pull value from array using dot notation.
*
* @param string $path
* @param string $default
*
* @return mixed
*/
protected function getResponseData($path, $default = null)
{
$array = $this->response;
if (!empty($path)) {
$keys = explode('.', $path);
foreach ($keys as $key) {
if (isset($array[$key])) {
$array = $array[$key];
} else {
return $default;
}
}
}
return $array;
}
/**
* Return all of the owner details available as an array.
*
* @return array
*/
public function toArray()
{
return $this->response;
}
}
How to use this Daylite provider & Daylite API
STEP 1: Start Authorization process
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/vendor/league/oauth2-client/src/Provider/Daylite.php';
require_once __DIR__ . '/vendor/league/oauth2-client/src/Provider/DayliteResourceOwner.php';
$daylite = new YOUR_NAMESPACE_BASE\OAuth2\Client\Provider\Daylite( [
'clientId' => 'YOUR_CLIENT_ID',
'clientSecret' => 'YOUR_CLIENT_SECRET',
'redirectUri' => 'YOUR_REDIRECT_URL',
] );
/**
* STEP 1
*
* Start Authorization process.
**/
// Fetch the authorization URL from the provider; this returns the
// urlAuthorize option and generates and applies any necessary parameters
// (e.g. state).
$authorizationUrl = $daylite->getAuthorizationUrl();
// Save the State somewhere, you will need it again in STEP 2
$oauth2state = $daylite->getState();
// Redirect the user to the authorization URL.
header( 'Location: ' . $authorizationUrl );
STEP 2: Finish Authorization steps
/**
* STEP 2
*
* Finish Authorization steps, after you've been
* redirected back to your specified URL by Daylite.
**/
// Check given state against previously stored one to mitigate CSRF attack
if ( empty( $_GET['state'] ) || ( $oauth2state !== $_GET['state'] ) ) {
error_log( 'ERROR: Could not finish Daylite authorisation: invalid state.' );
} else {
// Try to get an access token using the authorization code grant.
try {
// If successful, save this Token, you will need it again in STEP 3
// and to request refresh tokens in the future
$accessToken = $daylite->getAccessToken( 'authorization_code', [
'code' => $_GET['code']
] );
} catch ( \League\OAuth2\Client\Provider\Exception\IdentityProviderException $e ) {
// Failed to get the access token.
error_log( 'ERROR: ' . $e->getMessage() );
}
}
STEP 3: Using the API
/**
* STEP 3
*
* Using the API.
**/
try {
$request = $daylite->getAuthenticatedRequest(
'GET',
$daylite->getBaseAPIUrl() . '/v1/companies',
$accessToken
);
// Send request & collect response
$response = $daylite->getParsedResponse( $request );
} catch ( \League\OAuth2\Client\Provider\Exception\IdentityProviderException $e ) {
// Failed to get the response, record what went wrong.
error_log( 'ERROR: ' . $e->getMessage() . "\n\$request:\n" . print_r( $request, true ) );
}
STEP 4: Refreshing expired tokens
/**
* STEP 4
*
* Refreshing expired tokens.
*
* This needs to be a scheduled job, so that you'd
* be checking for token expiry & requesting a refresh tokens.
**/
// In my experience, Daylite would sometimes crash, when requesting refresh_token,
// AFTER current access_token has already expired,
// so I made sure to request new access_token/refresh_token pair 30 minutes early
$tokenExpiryTimeBufferMin = 30;
// Calculate if token has already expired
$tokenExpiryTimeBuffer = 60 * $tokenExpiryTimeBufferMin;
$tokenExpiresOn = $accessToken->getExpires();
$tokenExpiryMinusBuffer = $tokenExpiresOn - $tokenExpiryTimeBuffer;
$currentTime = time();
$tokenHasExpired = $tokenExpiryMinusBuffer <= $currentTime;
// If youre token expired, get a refreshed token
if ( $tokenHasExpired ) {
// Get a new token
try {
// If successful, save your new Token, you will need it again in STEP 3
// and to request refresh tokens in the future
$accessToken = $daylite->getAccessToken( 'refresh_token', [
'refresh_token' => $accessToken->getRefreshToken(),
] );
} catch ( \League\OAuth2\Client\Provider\Exception\IdentityProviderException $e ) { // InvalidArgumentException
if ( $e->getCode() == 401 && $e->getMessage() == 'invalid_grant' ) {
error_log( "ERROR: access token failed to refresh! Status Code: {$e->getCode()}, Error Message: '{$e->getMessage()}'" );
}
}
}