Search code examples
phpoauth-2.0

OAuth 2.0 MAC with PHP


I'm trying to authorize with Paysera using OAuth 2.0 MAC but I always get 401 error. Can you point me out on what I'm doing wrong?

Here is a specification: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-http-mac-02

Here is my code:

    private function _getTs() {
        return time();
    }

    private function _getNonce() {
        return uniqid();
    }

    private function _getMac($ts, $nonce, $method, $path, $host, $port, $key) {
        return base64_encode(hash_hmac("sha256", implode("\n", Array(
            $ts,
            $nonce,
            $method,
            $path,
            $host,
            $port,
            "",
        )), $key, true));
    }

    private function _getHeader($id, $method, $path, $host, $port, $key) {
        $ts = $this->_getTs();
        $nonce = $this->_getNonce();
        $mac = $this->_getMac($ts, $nonce, $method, $path, $host, $port, $key);

        $params = Array(
            "id" => $id,
            "ts" => $ts,
            "nonce" => $nonce,
            "mac" => $mac,
        );

        $parts = [];
        foreach ($params as $name => $value) {
            $parts[] = $name . '="' . $value . '"';
        }

        return "MAC " . implode(", ", $parts);
    }

    public function test() {
        $httpClient = new \Bitrix\Main\Web\HttpClient();
        $httpClient->setHeader("Authorization", $this->_getHeader(
            KEY_ID,
            "GET",
            "/rest/v1/post-offices",
            "delivery-api.paysera.com",
            443,
            KEY,
        ));
        $httpClient->query("GET", "https://delivery-api.paysera.com/rest/v1/post-offices");
        return $httpClient->getResult();
    }

Solution

  • Here is my final solution to that problem. Please pay attention that you have to enable delivery services and set them up on Paysera website, that was the main problem in my case:

    private function _getTs() {
        return time();
    }
    
    private function _getNonce() {
        return uniqid();
    }
    
    private function _getExt($body) {
        if ($body) {
            return http_build_query(Array(
                "body_hash" => base64_encode(hash("sha256", $body, true)),
            ));
        }
        return null;
    }
    
    private function _getMac($ts, $nonce, $method, $pathQuery, $host, $port, $ext, $key) {
        return base64_encode(hash_hmac("sha256", implode("\n", Array(
            $ts,
            $nonce,
            $method,
            $pathQuery,
            $host,
            $port,
            $ext,
            null,
        )), $key, true));
    }
    
    private function _getHeader($id, $method, $pathQuery, $host, $port, $body, $key) {
        $ts = $this->_getTs();
        $nonce = $this->_getNonce();
        $ext = $this->_getExt($body);
        $mac = $this->_getMac($ts, $nonce, $method, $pathQuery, $host, $port, $ext, $key);
    
        $mac = Array(
            "id" => $id,
            "ts" => $ts,
            "nonce" => $nonce,
            "mac" => $mac,
        );
    
        if ($ext) {
            $mac["ext"] = $ext;
        }
    
        return "MAC " . implode(", ", array_map(function($key, $value) {
            return $key . "=\"" . $value . "\"";
        }, array_keys($mac), $mac));
    }
    
    private function _getResult($method, $path, $arQuery = null, $arBody = null) {
        $url = self::_URL . $path;
    
        if ($arQuery && is_array($arQuery)) {
            $url = $url . "?" . http_build_query($arQuery);
        }
    
        $body = null;
        if ($arBody && is_array($arBody)) {
            $body = \Bitrix\Main\Web\Json::encode($arBody);
        }
    
        $uri = new \Bitrix\Main\Web\Uri($url);
        $pathQuery = $uri->getPathQuery();
        $host = $uri->getHost();
        $port = $uri->getPort();
    
        $header = $this->_getHeader(
            $this->_id,
            $method,
            $pathQuery,
            $host,
            $port,
            $body,
            $this->_key,
        );
    
        $cache = \Bitrix\Main\Data\Cache::createInstance();
        if ($cache->initCache(86400, \Bitrix\Main\Web\Json::encode(Array(
            "method" => $method,
            "path" => $path,
            "query" => $arQuery,
            "body" => $arBody,
        )))) { 
            $vars = $cache->getVars();
    
            return $vars;
        } elseif ($cache->startDataCache()) {
            $httpClient = new \Bitrix\Main\Web\HttpClient();
            $httpClient->setHeader("Authorization", $header);
            $httpClient->query($method, $url, $body);
            
            $result = $httpClient->getResult();
            $result = \Bitrix\Main\Web\Json::decode($result);
            
            $cache->endDataCache($result);
    
            return $result;
        }
    }
    
    public function getMethods($projectId = null, $fromCountryCode = null, $toCountryCode = null, $shipments = null) {
        $arBody = Array();
        if (is_string($projectId)) {
            $arBody["project_id"] = $projectId;
        }
        if (is_string($fromCountryCode)) {
            $arBody["from_country_code"] = $fromCountryCode;
        }
        if (is_string($toCountryCode)) {
            $arBody["to_country_code"] = $toCountryCode;
        }
        if (is_array($shipments)) {
            foreach ($shipments as $shipment) {
                if (is_int($shipment["weight"]) &&
                    is_int($shipment["width"]) &&
                    is_int($shipment["length"]) && 
                    is_int($shipment["height"])
                ) {
                    $arBody["shipments"][] = $shipment;
                }
            }
        }
    
        return $this->_getResult("PUT", "methods", null, $arBody)["list"];
    }