Search code examples
phpapirestoauth-2.0mautic

What are the best practices when redirecting users after OAuth 2.0 token renew?


I have implemented an Mautic API in a website. I use OAuth 2.0 to authenticate the communications between the two. The problem that I have is that I must renew the token from time to time, and in order to do that I have to provide a callback URL, I figured that I just have to use http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI] as my callback URL, that way, when the authentication or renew is done the user would be redirected to the last called URL. The problem is that sometimes the user is being redirected to the login page of the API to authorize the integration. That, to the best of my knowledge, should be done by me, only once.

In short, how can I avoid my users being shown the API authentication screen?

I haven't finished the integration yet; I still must work on some security issues. The class responsible for doing that is right below:

<?php

use Mautic\Auth\ApiAuth;
use Mautic\MauticApi;

class Mautic
{

   private static $instance;
   private $publicKey;
   private $secretKey;
   private $callback;
   private $baseURL;
   private $Api;
   private $ApiURL;
   private $auth;
   private $token;
   private $companyName;

   public function __construct()
   {
      $config = $this->getConfig();

      $this->publicKey   = $config['publicKey'];
      $this->secretKey   = $config['secretKey'];
      $this->baseURL     = $config['baseURL'];
      $this->companyName = $config['companyName'];

      $this->Api    = new MauticApi();
      $this->ApiURL = $this->baseURL . '/api/';

      if (!$this->isTokenValid()) {
         $this->getToken();
      }
   }

   /**
    * Read the config file "mautic.json" located in the root directory and returns an array with config values
    *
    * @return array
    */
   private function getConfig(): array
   {
      return $this->getJSON('mautic.json');
   }

   /**
    * Instantiates a new API class
    *
    * @param string $apiName
    * @return object
    */
   private function setApi(string $apiName): object
   {
      if(!$this->auth){
         $this->getToken();
      }
      return $this->Api->newApi($apiName, $this->auth, $this->ApiURL);
   }

   /**
    * Retorna la instancia de la clase actual
    *
    * @return object
    */
   public static function getInstance(): object
   {
      if (!self::$instance)
         self::$instance = new self();

      return self::$instance;
   }

   public function isTokenValid(): bool
   {
      $oldTokenExpiration = $this->checkForToken()['expires'];
      if (time() >= $oldTokenExpiration) {
         return false;
      }
      return true;
   }

   private function getToken($accessToken = null, $tokenExpiration = null, $refreshToken = null)
   {

      if ($previousToken = $this->checkForToken()) {
         $settings['accessToken']        = $previousToken['access_token'];
         $settings['accessTokenExpires'] = $previousToken['expires'];
         $settings['refreshToken']       = $previousToken['refresh_token'];
      }

      $settings  = [
         'baseUrl'      => $this->baseURL,     // Base URL of the Mautic instance
         'version'      => 'OAuth2',           // Version of the OAuth
         'clientKey'    => $this->publicKey,   // Client/Consumer key from Mautic
         'clientSecret' => $this->secretKey,   // Client/Consumer secret key from Mautic
         'callback'     => "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"
      ];

      if (isset($accessToken) && isset($tokenExpiration) && isset($refreshToken)) {
      }

      $initAuth = new ApiAuth();
      $auth     = $initAuth->newAuth($settings);

      // Initiate process for obtaining an access token; this will redirect the user to the authorize endpoint and/or set the tokens when the user is redirected back after granting authorization

      if ($auth->validateAccessToken()) {
         if ($auth->accessTokenUpdated()) {
            $accessTokenData = $auth->getAccessTokenData();
            $this->storeToken($accessTokenData);
            $this->auth = $auth;
            $this->token = $accessTokenData['access_token'];
            return $this->auth;
         }
      }
   }

   private function storeToken($accessTokenData)
   {
      $tokenInfo = json_encode($accessTokenData);
      file_put_contents("token.json", $tokenInfo);
   }

   /**
    * Read the file "token.json" located in the root directory and returns an array with any passed token values
    *
    * @return array
    */
   private function checkForToken(): array
   {
      return $this->getJSON('token.json');
   }

   /**
    * Reads a JSON file and returns its key and values as an array
    *
    * @param string $filePath
    * @return array
    */
   private function getJSON($filePath): array
   {
      if (!file_exists($filePath)) {
         return false;
      }
      $oldToken = file_get_contents($filePath);
      $oldToken = json_decode($oldToken);
      return (array) $oldToken;
   }

   /**
    * Creates a new contact
    *
    * @param string $name
    * @param string $phone
    * @param string $email
    * @param string $companyName
    * @return array
    */
   public function createContact(string $name, string $phone, string $email, int $companyName = null): array
   {

      if ($companyName == null) {
         $companyName = $this->getConfig()['companyName'];
      }

      $data = array(
         'firstname'          => $name,
         'phone'              => $phone,
         'email'              => $email,
         'company'            => $companyName,
         'ipAddress'          => $_SERVER['REMOTE_ADDR'],
         'overwriteWithBlank' => true,
      );
      $contactApi = $this->setApi('contacts');
      $newContact = $contactApi->create($data);
      return $newContact;
   }

   /**
    * Retorna los datos de un contacto
    *
    * @param int $contactId
    * @return object
    */
   public function getContact(int $contactId): object
   {
      return json_decode($this->curlGET("contacts", array($contactId)));
   }

   /**
    * Ejecuta una requisición GET al servidor de la API
    *
    * @param string $APIMethod
    * @param array $dataToSend
    * @return string
    */
   private function curlGET(string $APIMethod, array  $dataToSend = array()): string
   {

      $dataToSend["access_token"] = $this->token;
      $baseURL                    = $this->ApiURL . $APIMethod;
      $curl                       = curl_init();
      $curlOptions                = array(
         CURLOPT_URL              => $baseURL . '?' . http_build_query($dataToSend),
         CURLOPT_RETURNTRANSFER   => true,
         CURLOPT_SSL_VERIFYHOST   => false,
         CURLOPT_SSL_VERIFYPEER   => false
      );

      curl_setopt_array($curl, $curlOptions);

      $returnedData = curl_exec($curl);

      if (!$returnedData) {
         return curl_error($curl);
      } else {
         curl_close($curl);
         return $returnedData;
      }
   }
}

Solution

  • The problem seems to be re-authentication. Once you authenticate successfully you should not need to do that again and again.

    You get Token, Token Expires and Refresh Token when the process is complete. Here's complete example(https://tutorialsjoint.com/mautic-rest-api/).

    Once you have the token and you are checking if token is expired, you should use refresh token to obtain fresh access token. For some reason if your refresh token becomes invalid then only you need to re-authenticate, that usually happens when you change the client credentials.

    In your code I see you are doing authentication But can't see refresh token call, that should be your issue here.