GraphiQL Authorization has been denied for this request

I’m writing a PHP class to connect to GraphiQL. I got to the point of obtaining an access token, but when I try to execute a query, I get Authorization has been denied for this request.

Here is my method:

public function query($q)
    {
        // Check if access token is expired
        if (time() >= $this->conf->get_val('expires')) $this->login();
        
        $payload = http_build_query($q);

        $opts = [
            'http' => [
                'method' => 'POST',
                'ignore_errors' => true,
                'header'=> "Authorization: bearer ".$this->conf->get_val('atoken')
                    ."dataType: json"
                    ."Content-type: application/json\r\n"
                    ."Content-Length: " . strlen($payload) . "\r\n",
                'content' => $payload
            ]
        ];

        $c = stream_context_create($opts);

        $res = file_get_contents($this->build_url('api/graphql'), false, $c);

        print_r($res);
    }

Anyone familiar enough with PHP to help? Or do you think this is a setting in Samba?

Here is my entire class:

<?php
class Samba
{
    var $conf, $error_mess, $http_status;

    const HTTP_STATUS_OK = "HTTP/1.1 200 OK";

    function __construct()
    {
        $s = new Settings('SAMBA_CONFIG');
        $this->conf = $s;
        return true;
    }

    private function check_status($h = null)
    {
        if ($h)
        {
            if (is_array($h))
            {
                $this->http_status = $h[0];
                if ($this->http_status == self::HTTP_STATUS_OK)
                {
                    return true;
                }
            }
        }else{
            $this->error_mess = "Failed to connect to POS server!";
            return false;
        }
    }

    private function build_url($endpoint)
    {
        return $this->conf->get_val('proto')
            ."://".$this->conf->get_val('host')
            .":".$this->conf->get_val('port')
            ."/".$endpoint;
    }

    private function login()
    {
        $data = [
            'grant_type' => 'password',
            'username' => $this->conf->get_val('user'),
            'password' => $this->conf->get_val('pass'),
            'client_id' => $this->conf->get_val('client_id'),
            'client_secret' => $this->conf->get_val('client_secret')
        ];
        
        $payload = http_build_query($data);
        
        $opts = [
            'http' => [
                'method' => 'POST',
                'ignore_errors' => true,
                'header'=> "Content-type: application/x-www-form-urlencoded\r\n"
                    ."Content-Length: " . strlen($payload) . "\r\n",
                'content' => $payload
            ]
        ];
        
        $c = stream_context_create($opts);
        
        $res = file_get_contents($this->build_url('token'), false, $c);

        if($this->check_status($http_response_header))
        {
            $json = json_decode($res);
            $this->conf->set('atoken', $json->access_token);
            $this->conf->set('rtoken', $json->refresh_token);
            $this->conf->set('expires', time()+$json->expires_in);
            if ($this->conf->update())
            {
                return true;
            }else{
                $this->error_mess = $this->conf->error_mess;
                return false;
            }
        }
    }

    public function query($q)
    {
        // Check if access token is expired
        if (time() >= $this->conf->get_val('expires')) $this->login();
        
        $payload = http_build_query($q);

        $opts = [
            'http' => [
                'method' => 'POST',
                'ignore_errors' => true,
                'header'=> "Authorization: bearer ".$this->conf->get_val('atoken')
                    ."dataType: json"
                    ."Content-type: application/json\r\n"
                    ."Content-Length: " . strlen($payload) . "\r\n",
                'content' => $payload
            ]
        ];

        $c = stream_context_create($opts);

        $res = file_get_contents($this->build_url('api/graphql'), false, $c);

        print_r($res);
    }
}
?>

And here is my class instantiation:

$sam = new Samba;
$sam->query([
    "query" => "{getTickets{id}}",
    "variables" => null,
    "operationName" => null
]);
print_r($sam);

Result:

{
  "message": "Authorization has been denied for this request."
}

If you’re able to successfully obtain an access token but fail on the query it may because a) the access token is expired, or b) the access token is malformed.

What method are you using inside the http_build_query() wrapper?

Hi. Here is the usage for http_build_query from php.net:

http_build_query ( mixed $query_data [, string $numeric_prefix [, string $arg_separator [, int $enc_type = PHP_QUERY_RFC1738 ]]] ) : string

I’m not sure what you mean by method. I’m using POST for the http request, which is defined in the steam context function. I’m wondering about en_type though. It looks like the default is RFC1738. PHP says RFC3986 is also an option.

sorry, method = function

I’m not familiar with http_build_query
Here’s what I threw together (postman is your friend):

<?php

$query = '';
$accessToken = '';
$url = 'http://127.0.0.1:9000/api/graphql';
$method = 'POST';

$responseString = gqlApiQuery($query, $accessToken, $url, $method);

if ($responseString)
{
    $responseObj = json_decode($responseString);

    $jsonErrorMessage = '';
    switch (json_last_error())
    {
        case JSON_ERROR_NONE:
            break;

        case JSON_ERROR_DEPTH:
            $jsonErrorMessage = 'Maximum stack depth exceeded';
            break;

        case JSON_ERROR_STATE_MISMATCH:
            $jsonErrorMessage = 'Underflow or the modes mismatch';
            break;

        case JSON_ERROR_CTRL_CHAR:
            $jsonErrorMessage = 'Unexpected control character found';
            break;

        case JSON_ERROR_SYNTAX:
            $jsonErrorMessage = 'Malformed JSON';
            break;

        case JSON_ERROR_UTF8:
            $jsonErrorMessage = 'Malformed UTF-8 characters, possibly incorrectly encoded';
            break;

        default:
            $jsonErrorMessage = 'JSON - Other';
            break;
    }

    if ($jsonErrorMessage != '')
    {
        //LOG JSON ERROR
        exit();
    }

    if (!$responseObj->message)
    {
        if (!$responseObj->errors)
        {
            print ($responseString);
        }
        else
        {
            //LOG API ERROR
            print ($responseObj->errors->message);
        }
    }
    else
    {
        //LOG API ERROR
        print ($responseObj->message);
    }
}
else
{
    //LOG NULL RESPONSE ERROR
}

function gqlApiQuery($query, $accessToken, $url, $method)
{
    $curl = curl_init();

    curl_setopt_array($curl, array(
        CURLOPT_URL => "$url",
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => '',
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 0,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "$method",
        CURLOPT_POSTFIELDS =>"$query",
        CURLOPT_HTTPHEADER => array(
            "Authorization: Bearer $accessToken",
            'dataType: json',
            'Content-Type: application/json'
        ),
    ));

    $response = curl_exec($curl);

    curl_close($curl);
    return $response;
}

Tested and verified working

EDIT:
added handling for the exact message you’ve been receiving

1 Like

Yeah I think your function is accomplishing the same thing. My function is a special PHP wrapper to reduce lines of code. I got the same error unfortunately.

Here is my new class with your code adapted:

<?php
class Samba
{
    var $conf, $error_mess, $http_status;

    const HTTP_STATUS_OK = "HTTP/1.1 200 OK";

    function __construct()
    {
        $s = new Settings('SAMBA_CONFIG');
        $this->conf = $s;
        return true;
    }

    private function check_status($h = null)
    {
        if ($h)
        {
            if (is_array($h))
            {
                $this->http_status = $h[0];
                if ($this->http_status == self::HTTP_STATUS_OK)
                {
                    return true;
                }
            }
        }else{
            $this->error_mess = "Failed to connect to POS server! [".$this->http_status."]";
            return false;
        }
    }

    private function build_url($endpoint)
    {
        return $this->conf->get_val('proto')
            ."://".$this->conf->get_val('host')
            .":".$this->conf->get_val('port')
            ."/".$endpoint;
    }

    private function login()
    {
        $data = [
            'grant_type' => 'password',
            'username' => $this->conf->get_val('user'),
            'password' => $this->conf->get_val('pass'),
            'client_id' => $this->conf->get_val('client_id'),
            'client_secret' => $this->conf->get_val('client_secret')
        ];
        
        $payload = http_build_query($data);
        
        $opts = [
            'http' => [
                'method' => 'POST',
                'ignore_errors' => true,
                'header'=> "Content-type: application/x-www-form-urlencoded\r\n"
                    ."Content-Length: " . strlen($payload) . "\r\n",
                'content' => $payload
            ]
        ];
        
        $c = stream_context_create($opts);
        
        $res = file_get_contents($this->build_url('token'), false, $c);

        if($this->check_status($http_response_header))
        {
            $json = json_decode($res);
            $this->conf->set('atoken', $json->access_token);
            $this->conf->set('rtoken', $json->refresh_token);
            $this->conf->set('expires', time()+$json->expires_in);
            if ($this->conf->update())
            {
                return true;
            }else{
                $this->error_mess = $this->conf->error_mess;
                return false;
            }
        }
    }

    function query($q)
    {
        // Check if access token is expired
        if (time() >= $this->conf->get_val('expires')) $this->login();

        // CURL setup
        $url = $this->build_url('api/graphql');
        $method = 'POST';
        
        $curl = curl_init();
        
        curl_setopt_array($curl, array(
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 0,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_POSTFIELDS => $q,
            CURLOPT_HTTPHEADER => array(
                "Authorization: Bearer ".$this->conf->atoken,
                'dataType: json',
                'Content-Type: application/json'
            ),
        ));
    
        $responseString = curl_exec($curl);
        curl_close($curl);

        if ($responseString)
        {
            $responseObj = json_decode($responseString);
        
            $jsonErrorMessage = '';
            switch (json_last_error())
            {
                case JSON_ERROR_NONE:
                    break;
        
                case JSON_ERROR_DEPTH:
                    $jsonErrorMessage = 'Maximum stack depth exceeded';
                    break;
        
                case JSON_ERROR_STATE_MISMATCH:
                    $jsonErrorMessage = 'Underflow or the modes mismatch';
                    break;
        
                case JSON_ERROR_CTRL_CHAR:
                    $jsonErrorMessage = 'Unexpected control character found';
                    break;
        
                case JSON_ERROR_SYNTAX:
                    $jsonErrorMessage = 'Malformed JSON';
                    break;
        
                case JSON_ERROR_UTF8:
                    $jsonErrorMessage = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                    break;
        
                default:
                    $jsonErrorMessage = 'JSON - Other';
                    break;
            }
        
            if (!empty($jsonErrorMessage))
            {
                $this->error_mess = $jsonErrorMessage;
                return false;
            }
        
            if (!$responseObj->message)
            {
                if (!$responseObj->errors)
                {
                    print_r($responseObj);
                }
                else
                {
                    //LOG API ERROR
                    $this->error_mess = $responseObj->errors->message;
                    return false;
                }
            }
            else
            {
                //LOG API ERROR
                $this->error_mess = $responseObj->message;
                return false;
            }
        }
        else
        {
            //LOG NULL RESPONSE ERROR
        }
    }
}
?>

Are there any Samba log files I can look at? I have all functions turned on in Samba. This is driving my nuts!

Never mind. I was incorrectly calling atoken from setttings. That was not the original problem though. I’m no longer getting “Authorization denied”. Now I’m getting “An error occured”. More in a bit.

1 Like

I got it! It works!! I forgot to json_encode() the query lol.

I’ll publish my class once it’s done, with all handlers and such. Still need to detect invalid access token and exchange refresh for access.

Thanks Memo.

1 Like

When there is an invalid access token message will be the only thing returned in the JSON response which I handled here:

if (!$responseObj->message)
    {
        if (!$responseObj->errors)
        {
            print ($responseString);
        }
        else
        {
            //LOG API ERROR
            print ($responseObj->errors->message);
        }
    }
    else
    {
        //LOG API ERROR
        print ($responseObj->message);
    }

Regarding refresh tokens, I found it easier to just store the access token and expiration date (minus 5 mins) and compare the expiration datetime with now to see if a new access token is required.

Here is my completed working PHP class:

<?php
class Samba
{
    var $conf, $error_mess, $http_status;
    var $auth_method = "access_token";
    var $retries = 0;
    var $res;

    const HTTP_STATUS_OK = "HTTP/1.1 200 OK";
    const HTTP_STATUS_BAD_REQ = "HTTP/1.1 400 Bad Request";
    const CURL_HTTP_CODE_UNAUTH = 401;
    const CURL_HTTP_CODE_OK = 200;
    const MAX_RETRIES = 5;

    function __construct()
    {
        $s = new Settings('SAMBA_CONFIG');
        $this->conf = $s;
        return true;
    }

    private function auth_log($stat)
    {
        $fields = [
            'auth_method',
            'auth_user',
            'auth_status'
        ];
        $vals = [
            $this->auth_method,
            $this->conf->get_val('user'),
            $stat
        ];
        global $db;
        $db->insert('api_auth_log', $fields, $vals);
    }

    private function check_status($h = null)
    {
        if ($h)
        {
            if (is_array($h))
            {
                $this->http_status = $h[0];
                switch ($this->http_status)
                {
                    case self::HTTP_STATUS_OK:
                        $this->auth_log('OK');
                        return true;
                    break;
                    case self::HTTP_STATUS_BAD_REQ:
                        if ($this->retries < self::MAX_RETRIES)
                        {
                            $this->auth_log('FAIL');
                            $this->conf->set('atoken', '');
                            $this->conf->set('rtoken', '');
                            $this->conf->update();
                            return $this->login();
                        }else{
                            $this->error_mess = "Max login retries [".self::MAX_RETRIES."] exceeded!";
                            return false;
                        }
                    break;
                    default:
                        $this->error_mess = "An HTTP error occurred [".$this->http_status."]";
                        return false;
                }
            }
        }else{
            $this->error_mess = "Failed to connect to POS server! [".$this->http_status."]";
            return false;
        }
    }

    private function build_url($endpoint)
    {
        return $this->conf->get_val('proto')
            ."://".$this->conf->get_val('host')
            .":".$this->conf->get_val('port')
            ."/".$endpoint;
    }

    private function login()
    {
        $do_refresh = false;
        if (!empty($this->conf->get_val('rtoken')))
        {
            $this->auth_method = "refresh_token";
            $data = [
                'grant_type' => $this->auth_method,
                'refresh_token' => $this->conf->get_val('rtoken')
            ];
            $do_refresh = true;
        }else{
            $this->auth_method = "password";
            $data = [
                'grant_type' => $this->auth_method,
                'username' => $this->conf->get_val('user'),
                'password' => $this->conf->get_val('pass')
            ];
        }
        $data['client_id'] = $this->conf->get_val('client_id');
        $data['client_secret'] = $this->conf->get_val('client_secret');
        
        $payload = http_build_query($data);
        
        $opts = [
            'http' => [
                'method' => 'POST',
                'ignore_errors' => true,
                'header'=> "Content-type: application/x-www-form-urlencoded\r\n"
                    ."Content-Length: " . strlen($payload) . "\r\n",
                'content' => $payload
            ]
        ];
        
        $c = stream_context_create($opts);
        
        $res = file_get_contents($this->build_url('token'), false, $c);

        if($this->check_status($http_response_header))
        {
            $json = json_decode($res);
            $this->conf->set('atoken', $json->access_token);
            $this->conf->set('rtoken', $json->refresh_token);
            $this->conf->set('expires', time()+$json->expires_in);
            if ($this->conf->update())
            {
                return true;
            }else{
                $this->error_mess = $this->conf->error_mess;
                return false;
            }
        }
    }

    function query($q, $from_file=1)
    {
        // Load query file
        if ($from_file)
        {
            $qf = GQL_DIR."/".$q.".graphql";
            if (file_exists($qf))
            {
                $q = file_get_contents($qf);
            }else{
                $this->error_mess = "Query file not found [".$q."]";
                return false;
            }
        }

        $query = [
            "query" => $q,
            "variables" => null,
            "operationName" => null
        ];

        // Check if access token is expired
        if (time() >= $this->conf->get_val('expires')) $this->login();

        // CURL setup
        $url = $this->build_url('api/graphql');
        $method = 'POST';
        
        $curl = curl_init();
        
        curl_setopt_array($curl, array(
            CURLINFO_HEADER_OUT => true,
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 0,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_POSTFIELDS => json_encode($query),
            CURLOPT_HTTPHEADER => array(
                "Authorization: Bearer ".$this->conf->get_val('atoken'),
                'dataType: json',
                'Content-Type: application/json'
            ),
        ));
    
        $responseString = curl_exec($curl);

        $info = curl_getinfo($curl);
        //print_r($info); die();

        // Check HTTP status
        if ($info['http_code'] != self::CURL_HTTP_CODE_OK)
        {
            if ($info['http_code'] == self::CURL_HTTP_CODE_UNAUTH)
            {
                if (!$this->login()) return false;
                return $this->query($q);
            }else{
                $this->error_mess = "An HTTP error occurred [".$info['http_code']."]";
                return false;
            }
        }

        curl_close($curl);

        if ($responseString)
        {
            $responseObj = json_decode($responseString);

            // Test for empty object
            if (empty((array)$responseObj->data))
            {
                $this->error_mess = "Server returned no data!";
                return false;
            }
        
            $jsonErrorMessage = '';
            switch (json_last_error())
            {
                case JSON_ERROR_NONE:
                    break;
        
                case JSON_ERROR_DEPTH:
                    $jsonErrorMessage = 'Maximum stack depth exceeded';
                    break;
        
                case JSON_ERROR_STATE_MISMATCH:
                    $jsonErrorMessage = 'Underflow or the modes mismatch';
                    break;
        
                case JSON_ERROR_CTRL_CHAR:
                    $jsonErrorMessage = 'Unexpected control character found';
                    break;
        
                case JSON_ERROR_SYNTAX:
                    $jsonErrorMessage = 'Malformed JSON';
                    break;
        
                case JSON_ERROR_UTF8:
                    $jsonErrorMessage = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                    break;
        
                default:
                    $jsonErrorMessage = 'JSON - Other';
                    break;
            }
        
            if (!empty($jsonErrorMessage))
            {
                $this->error_mess = $jsonErrorMessage;
                return false;
            }
        
            if (!isset($responseObj->message) || empty($responseObj->message))
            {
                if (!$responseObj->errors)
                {
                    $this->res = $responseObj;
                    return $this->res;
                }
                else
                {
                    //LOG API ERROR
                    $this->error_mess = $responseObj->errors->message;
                    return false;
                }
            }
            else
            {
                //LOG API ERROR
                $this->error_mess = $responseObj->message;
                return false;
            }
        }
        else
        {
            //LOG NULL RESPONSE ERROR
            $this->error_mess = "No response from server!";
            return false;
        }
    }
}
?>
3 Likes