I cannot retrieve Access Token

Hi i’m trying do a login on my website using twitch and i followed the steps in https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-authorization-code-flow but at the last step also if i do the correct POST request, nothing is returned…

The code is the following:

public function getTwitchAccessToken( $code, $redirectUri ) {
		// requet endpoint
		$endpoint = self::TWITCH_ID_DOMAIN . 'oauth2/token';

		$apiParams = array( // params for our api call
			'endpoint' => $endpoint,
			'type' => 'POST',
			'url_params' => array(
				'client_id' => $this->_clientId,
				'client_secret' => $this->_clientSecret,
				'code' => $code,
				'grant_type' => 'authorization_code',
				'redirect_uri' => $redirectUri,
			)
		);

		// make api call and return response
		return $this->makeApiCall( $apiParams );
	}

	/**
	 * Make calls to the Twitch API
	 *
	 * @param array $params
	 *
	 * @return array
	 */
	public function makeApiCall( $params ) {
		$curlOptions = array( // curl options
			CURLOPT_URL => $params['endpoint'], // endpoint
			CURLOPT_CAINFO => PATH_TO_CERT, // ssl certificate
			CURLOPT_RETURNTRANSFER => TRUE, // return stuff!
			CURLOPT_SSL_VERIFYPEER => TRUE, // verify peer
			CURLOPT_SSL_VERIFYHOST => 2, // verify host
		);

		if ( isset( $params['authorization'] ) ) { // we need to pass along headers with the request
			$curlOptions[CURLOPT_HEADER] = TRUE;
			$curlOptions[CURLOPT_HTTPHEADER] = $params['authorization'];
		}

		if ( 'POST' == $params['type'] ) { // post request things
			$curlOptions[CURLOPT_POST] = TRUE;
            $curlOptions[CURLOPT_POSTFIELDS] = http_build_query( $params['url_params'] );
			
		} elseif ( 'GET' == $params['type'] ) { // get request things
			$curlOptions[CURLOPT_URL] .= '?' . http_build_query( $params['url_params'] );
		}

		// initialize curl
		$ch = curl_init();

		// set curl options
		curl_setopt_array( $ch, $curlOptions );

		// make call
		$apiResponse = curl_exec( $ch );
		if ( isset( $params['authorization'] ) ) { // we have headers to deal with
			// get size of header
			$headerSize = curl_getinfo( $ch, CURLINFO_HEADER_SIZE );

			// remove header from response so we are left with json body
			$apiResponseBody = substr( $apiResponse, $headerSize );

			// json decode response body
			$apiResponse = json_decode( $apiResponseBody, true );	
		} else { // no headers response is json string
			// json decode response body
			$apiResponse = json_decode( $apiResponse, true );
		}
		
		// close curl
		curl_close( $ch );
		return array(
			'status' => isset( $apiResponse['status'] ) ? 'fail' : 'ok', // if status then there was an error
			'message' => isset( $apiResponse['message'] ) ? $apiResponse['message'] : '', // if message return it
			'api_data' => $apiResponse, // api response data
			'endpoint' => $curlOptions[CURLOPT_URL], // endpoint hit
			'url_params' => $params['url_params'] // url params sent with the request
		);
	}

What am i doing wrong?

1 Like

This feels like a convoluted way to do this in PHP.

You most liekly do not need these four lines

			CURLOPT_CAINFO => PATH_TO_CERT, // ssl certificate
			CURLOPT_RETURNTRANSFER => TRUE, // return stuff!
			CURLOPT_SSL_VERIFYPEER => TRUE, // verify peer
			CURLOPT_SSL_VERIFYHOST => 2, // verify host

and here

			// json decode response body
			$apiResponse = json_decode( $apiResponseBody, true );	
		} else { // no headers response is json string
			// json decode response body
			$apiResponse = json_decode( $apiResponse, true );
		}

you assume you get JSON, you might not have done

There is not HTTP Response code checking here either, so you didn’t test if you got a 4xx as apposed to a 2xx code.

This “PHP One Page” example might help you out

Specifically the stuff around line 49 for HTTP Response code checking

		$endpoint = self::TWITCH_ID_DOMAIN . 'oauth2/token';

What is self::TWITCH_ID_DOMAIN set to?

Did you omit a / on the end and it tried to POST to https://id.twitch.tvoauth2/token in error?

TWITCH_ID_DOMAIN is set to https://id.twitch.tv/.
I assume I get JSON, but it’s what the documentation says, that I get a JSON.
If something goes wrong it’s chechek with status to see if it’s set to fail or ok. So i don’t check inside the function i put but in the other ones.

<?php
/**
 * Class for handling all calls to the Twitch API
 *
 * @author Justin Stolpe
 */
class eciTwitchApi {
	/**
	 * @var api authorization domain
	 */
	const TWITCH_ID_DOMAIN = 'https://id.twitch.tv/';

	/**
	 * @var api endpoint calls domain
	 */
	const TWITCH_API_DOMAIN = 'https://api.twitch.tv/helix/';

	/**
	 * @var client id
	 */
	private $_clientId;

	/**
	 * @var client secret
	 */
	private $_clientSecret;

	/**
	 * @var access token
	 */
	private $_accessToken;

	/**
	 * @var refresh token
	 */
	private $_refreshToken;

	/**
	 * Constructor for this class
	 *
	 * @param string $clientId twitch client id
	 * @param string $clientSecret twitch client secret
	 * @param string $accessToken twitch access token
	 *
	 * @return void
	 */
	public function __construct( $clientId, $clientSecret, $accessToken = '' ) {
		// set client id
		$this->_clientId = $clientId;

		// set client secret
		$this->_clientSecret = $clientSecret;

		// set access token
		$this->_accessToken = $accessToken;
	}

	
	/**
	 * Get the login url
	 *
	 * @param array $redirectUri
	 *
	 * @return string
	 */
	public function getLoginUrl( $redirectUri ) {
		// request endpoint
		$endpoint = self::TWITCH_ID_DOMAIN . 'oauth2/authorize';

		// store state so we can check it once the user comes back to our redirect uri
		$_SESSION['twitch_state'] = md5( microtime() . mt_rand() );

		$params = array( // params for endpoint
			'client_id' => $this->_clientId,
			'redirect_uri' => $redirectUri,
			'response_type' => 'code',
			'state' => $_SESSION['twitch_state']
		);

		// add params to endpoint and return the login url
		return $endpoint . '?' . http_build_query( $params );
	}

	/**
	 * Try and log a user in with Twitch
	 *
	 * @param string $code code from Twitch
	 * @param string $redirectUri redirect uri
	 *
	 * @return array
	 */
	public function tryAndLoginWithTwitch( $code, $redirectUri ) {
		// get access token
		$accessToken = $this->getTwitchAccessToken( $code, $redirectUri );

		// save status and message from access token call
		$status = $accessToken['status'];
		$message = $accessToken['message'];
		
		if ( 'ok' == $status ) { // we got an access token
			// set access token and refresh token class vars 
			$this->_accessToken = $accessToken['api_data']['access_token'];
			$this->_refreshToken = $accessToken['api_data']['refresh_token'];

			// get user info
			$userInfo = $this->getUserInfo();

			// save status and message from get user info call
			$status = $userInfo['status'];
			$message = $userInfo['message'];
			
			if ( 'ok' == $status && isset( $userInfo['api_data']['data'][0] ) ) { // we have user info!
				// log user in with info from get user info api call
				$this->_logUserInWithTwitch( $userInfo['api_data']['data'][0] );
			}
		}

		return array( // return status and message of login
			'status' => $status,
			'message' => $message
		);
	}

	/**
	 * Log a user in
	 *
	 * @param array $apiUserInfo user info from Twitch
	 * 
	 * @return void
	 */
	private function _logUserInWithTwitch( $apiUserInfo ) {
		// save user info and tokens in the session
		$_SESSION['twitch_user_info'] = $apiUserInfo;
		$_SESSION['twitch_user_info']['access_token'] = $this->_accessToken;
		$_SESSION['twitch_user_info']['refresh_token'] = $this->_refreshToken;
	
		// check for user with twitch id
		$userInfo = getRowWithValue( 'users', 'twitch_user_id', $apiUserInfo['id'] );

		if( $userInfo ) {
			// refresh twitch tokens of the user
			updateRow( 'users', 'twitch_access_token', $this->_accessToken, $userId );
			updateRow( 'users', 'twitch_refresh_token', $this->_refreshToken, $userId );
		} else {
			$queryUserInfo = getUserInfo($apiUserInfo['id']);
			$signupUserInfo = array( // data we need to insert the user in our database
				'twitch_user_id' => $apiUserInfo['id'], // Twitch id from Twitch response
				'username' => $queryUserInfo['data']['display_name'],
				'twitch_access_token' => $this->_accessToken, // access token from Twitch response
				'twitch_refresh_token' => $this->_refreshToken // refresh token from Twitch response
			);

			// sign user up
			$userId = signUserUp( $signupUserInfo );

			// get user info
			$userInfo = getRowWithValue( 'users', 'twitch_user_id', $apiUserInfo['id'] );

			// update session so the user is logged in
			$_SESSION['is_logged_in'] = true;
			$_SESSION['user_info'] = $userInfo;
		}
	}

	/**
	 * Get a users info from Twitch
	 *
	 * @param void
	 *
	 * @return array
	 */
	public function getUserInfo() {
		// requet endpoint
		$endpoint = self::TWITCH_API_DOMAIN . 'users';

		$apiParams = array( // params for our api call
			'endpoint' => $endpoint,
			'type' => 'GET',
			'authorization' => $this->getAuthorizationHeaders(),
			'url_params' => array()
		);

		// make api call and return response
		return $this->makeApiCall( $apiParams );
	}

	/**
	 * Get authorization header for api call
	 *
	 * @param void
	 *
	 * @return array
	 */
	public function getAuthorizationHeaders() {
		return array( // this array will be used as the header for the api call
			'Client-ID: ' . $this->_clientId,
			'Authorization: Bearer ' . $this->_accessToken
		);
	}

	/**
	 * Get access token
	 *
	 * @param string $code code from Twitch
	 * @param string $redirectUri redirect uri
	 *
	 * @return array
	 */
	public function getTwitchAccessToken( $code, $redirectUri ) {
		// requet endpoint
		$endpoint = self::TWITCH_ID_DOMAIN . 'oauth2/token';

		$apiParams = array( // params for our api call
			'endpoint' => $endpoint,
			'type' => 'POST',
			'url_params' => array(
				'client_id' => $this->_clientId,
				'client_secret' => $this->_clientSecret,
				'code' => $code,
				'grant_type' => 'authorization_code',
				'redirect_uri' => $redirectUri,
			)
		);

		// make api call and return response
		return $this->makeApiCall( $apiParams );
	}

	/**
	 * Make calls to the Twitch API
	 *
	 * @param array $params
	 *
	 * @return array
	 */
	public function makeApiCall( $params ) {
		$curlOptions = array( // curl options
			CURLOPT_URL => $params['endpoint'], // endpoint
			CURLOPT_CAINFO => PATH_TO_CERT, // ssl certificate
			CURLOPT_RETURNTRANSFER => TRUE, // return stuff!
			CURLOPT_SSL_VERIFYPEER => TRUE, // verify peer
			CURLOPT_SSL_VERIFYHOST => 2, // verify host
		);

		if ( isset( $params['authorization'] ) ) { // we need to pass along headers with the request
			$curlOptions[CURLOPT_HEADER] = TRUE;
			$curlOptions[CURLOPT_HTTPHEADER] = $params['authorization'];
		}

		if ( 'POST' == $params['type'] ) { // post request things
			$curlOptions[CURLOPT_POST] = TRUE;
            $curlOptions[CURLOPT_POSTFIELDS] = http_build_query( $params['url_params'] );
			
		} elseif ( 'GET' == $params['type'] ) { // get request things
			$curlOptions[CURLOPT_URL] .= '?' . http_build_query( $params['url_params'] );
		}

		// initialize curl
		$ch = curl_init();

		// set curl options
		curl_setopt_array( $ch, $curlOptions );

		// make call
		$apiResponse = curl_exec( $ch );
		if ( isset( $params['authorization'] ) ) { // we have headers to deal with
			// get size of header
			$headerSize = curl_getinfo( $ch, CURLINFO_HEADER_SIZE );

			// remove header from response so we are left with json body
			$apiResponseBody = substr( $apiResponse, $headerSize );

			// json decode response body
			$apiResponse = json_decode( $apiResponseBody, true );	
		} else { // no headers response is json string
			// json decode response body
			$apiResponse = json_decode( $apiResponse, true );
		}
		
		// close curl
		curl_close( $ch );
		return array(
			'status' => isset( $apiResponse['status'] ) ? 'fail' : 'ok', // if status then there was an error
			'message' => isset( $apiResponse['message'] ) ? $apiResponse['message'] : '', // if message return it
			'api_data' => $apiResponse, // api response data
			'endpoint' => $curlOptions[CURLOPT_URL], // endpoint hit
			'url_params' => $params['url_params'] // url params sent with the request
		);
	}
}

That’s the whole code btw.

GetLoginUrl is used inside my index.php to set the href value of the button.

1 Like

Never assume.
You could get a partial download
You could get invalid JSON
Something else could happen.
API could malfunction
You could get a proxy/cache error
You could get a fastly error.

Thats why you should test the HTTP code, and use json_last_error to test if the response parsed as JSON.

When you say nothing is returned. What do you mean nothing is returned.

Are you getting no HTTP Status code?
No response body?
No what? You should be able to adjust your logic/code to catch a nothing.

You must get something even if it’s a PHP or cURL error

In theory this section is wrong

        if ( 'POST' == $params['type'] ) { // post request things
            $curlOptions[CURLOPT_POST] = TRUE;
            $curlOptions[CURLOPT_POSTFIELDS] = http_build_query( $params['url_params'] );

The documentation states

POST https://id.twitch.tv/oauth2/token
    ?client_id=<your client ID>
    &client_secret=<your client secret>
    &code=<authorization code received above>
    &grant_type=authorization_code
    &redirect_uri=<your registered redirect URI>

Which means you post them as query string arguments
However last I checked the endpoint accepted query string as well as form post. So that shouldn’t be an issue.

Your “library” code here is just force posting params as a “form”.

Whaths the purpose of this

        if ( isset( $params['authorization'] ) ) { // we have headers to deal with
            // get size of header
            $headerSize = curl_getinfo( $ch, CURLINFO_HEADER_SIZE );

            // remove header from response so we are left with json body
            $apiResponseBody = substr( $apiResponse, $headerSize );

            // json decode response body
            $apiResponse = json_decode( $apiResponseBody, true );   
        } else { // no headers response is json string
            // json decode response body
            $apiResponse = json_decode( $apiResponse, true );
        }

You don’t even do anything with the headers? So you are extracing the headers and doing nothing with it?

I tested your code, it complained about CURLOPT_CAINFO

So I removed

			CURLOPT_CAINFO => PATH_TO_CERT, // ssl certificate
			CURLOPT_SSL_VERIFYPEER => TRUE, // verify peer
			CURLOPT_SSL_VERIFYHOST => 2, // verify host

As these lines shouldn’t be needed.
And I don’t have Twitch’s SSL Certo downloaded to reference, since my cURL bunde is valid/up todate

CURLOPT_SSL_VERIFYHOST default is 2 anyway.

<?php
/**
 * Class for handling all calls to the Twitch API
 *
 * @author Justin Stolpe
 */
class eciTwitchApi {
    /**
     * @var api authorization domain
     */
    const TWITCH_ID_DOMAIN = 'https://id.twitch.tv/';

    /**
     * @var api endpoint calls domain
     */
    const TWITCH_API_DOMAIN = 'https://api.twitch.tv/helix/';

    /**
     * @var client id
     */
    private $_clientId;

    /**
     * @var client secret
     */
    private $_clientSecret;

    /**
     * @var access token
     */
    private $_accessToken;

    /**
     * @var refresh token
     */
    private $_refreshToken;

    /**
     * Constructor for this class
     *
     * @param string $clientId twitch client id
     * @param string $clientSecret twitch client secret
     * @param string $accessToken twitch access token
     *
     * @return void
     */
    public function __construct( $clientId, $clientSecret, $accessToken = '' ) {
        // set client id
        $this->_clientId = $clientId;

        // set client secret
        $this->_clientSecret = $clientSecret;

        // set access token
        $this->_accessToken = $accessToken;
    }


    /**
     * Get the login url
     *
     * @param array $redirectUri
     *
     * @return string
     */
    public function getLoginUrl( $redirectUri ) {
        // request endpoint
        $endpoint = self::TWITCH_ID_DOMAIN . 'oauth2/authorize';

        // store state so we can check it once the user comes back to our redirect uri
        $_SESSION['twitch_state'] = md5( microtime() . mt_rand() );

        $params = array( // params for endpoint
            'client_id' => $this->_clientId,
            'redirect_uri' => $redirectUri,
            'response_type' => 'code',
            'state' => $_SESSION['twitch_state']
        );

        // add params to endpoint and return the login url
        return $endpoint . '?' . http_build_query( $params );
    }

    /**
     * Try and log a user in with Twitch
     *
     * @param string $code code from Twitch
     * @param string $redirectUri redirect uri
     *
     * @return array
     */
    public function tryAndLoginWithTwitch( $code, $redirectUri ) {
        // get access token
        $accessToken = $this->getTwitchAccessToken( $code, $redirectUri );

        // save status and message from access token call
        $status = $accessToken['status'];
        $message = $accessToken['message'];

        if ( 'ok' == $status ) { // we got an access token
            // set access token and refresh token class vars
            $this->_accessToken = $accessToken['api_data']['access_token'];
            $this->_refreshToken = $accessToken['api_data']['refresh_token'];

            // get user info
            $userInfo = $this->getUserInfo();

            // save status and message from get user info call
            $status = $userInfo['status'];
            $message = $userInfo['message'];

            if ( 'ok' == $status && isset( $userInfo['api_data']['data'][0] ) ) { // we have user info!
                // log user in with info from get user info api call
                $this->_logUserInWithTwitch( $userInfo['api_data']['data'][0] );
            }
        }

        return array( // return status and message of login
            'status' => $status,
            'message' => $message
        );
    }

    /**
     * Log a user in
     *
     * @param array $apiUserInfo user info from Twitch
     *
     * @return void
     */
    private function _logUserInWithTwitch( $apiUserInfo ) {
        // save user info and tokens in the session
        $_SESSION['twitch_user_info'] = $apiUserInfo;
        $_SESSION['twitch_user_info']['access_token'] = $this->_accessToken;
        $_SESSION['twitch_user_info']['refresh_token'] = $this->_refreshToken;

        // check for user with twitch id
        $userInfo = getRowWithValue( 'users', 'twitch_user_id', $apiUserInfo['id'] );

        if( $userInfo ) {
            // refresh twitch tokens of the user
            updateRow( 'users', 'twitch_access_token', $this->_accessToken, $userId );
            updateRow( 'users', 'twitch_refresh_token', $this->_refreshToken, $userId );
        } else {
            $queryUserInfo = getUserInfo($apiUserInfo['id']);
            $signupUserInfo = array( // data we need to insert the user in our database
                'twitch_user_id' => $apiUserInfo['id'], // Twitch id from Twitch response
                'username' => $queryUserInfo['data']['display_name'],
                'twitch_access_token' => $this->_accessToken, // access token from Twitch response
                'twitch_refresh_token' => $this->_refreshToken // refresh token from Twitch response
            );

            // sign user up
            $userId = signUserUp( $signupUserInfo );

            // get user info
            $userInfo = getRowWithValue( 'users', 'twitch_user_id', $apiUserInfo['id'] );

            // update session so the user is logged in
            $_SESSION['is_logged_in'] = true;
            $_SESSION['user_info'] = $userInfo;
        }
    }

    /**
     * Get a users info from Twitch
     *
     * @param void
     *
     * @return array
     */
    public function getUserInfo() {
        // requet endpoint
        $endpoint = self::TWITCH_API_DOMAIN . 'users';

        $apiParams = array( // params for our api call
            'endpoint' => $endpoint,
            'type' => 'GET',
            'authorization' => $this->getAuthorizationHeaders(),
            'url_params' => array()
        );

        // make api call and return response
        return $this->makeApiCall( $apiParams );
    }

    /**
     * Get authorization header for api call
     *
     * @param void
     *
     * @return array
     */
    public function getAuthorizationHeaders() {
        return array( // this array will be used as the header for the api call
            'Client-ID: ' . $this->_clientId,
            'Authorization: Bearer ' . $this->_accessToken
        );
    }

    /**
     * Get access token
     *
     * @param string $code code from Twitch
     * @param string $redirectUri redirect uri
     *
     * @return array
     */
    public function getTwitchAccessToken( $code, $redirectUri ) {
        // requet endpoint
        $endpoint = self::TWITCH_ID_DOMAIN . 'oauth2/token';

        $apiParams = array( // params for our api call
            'endpoint' => $endpoint,
            'type' => 'POST',
            'url_params' => array(
                'client_id' => $this->_clientId,
                'client_secret' => $this->_clientSecret,
                'code' => $code,
                'grant_type' => 'authorization_code',
                'redirect_uri' => $redirectUri,
            )
        );

        // make api call and return response
        return $this->makeApiCall( $apiParams );
    }

    /**
     * Make calls to the Twitch API
     *
     * @param array $params
     *
     * @return array
     */
    public function makeApiCall( $params ) {
        $curlOptions = array( // curl options
            CURLOPT_URL => $params['endpoint'], // endpoint
            CURLOPT_RETURNTRANSFER => TRUE, // return stuff!
        );

        if ( isset( $params['authorization'] ) ) { // we need to pass along headers with the request
            $curlOptions[CURLOPT_HEADER] = TRUE;
            $curlOptions[CURLOPT_HTTPHEADER] = $params['authorization'];
        }

        if ( 'POST' == $params['type'] ) { // post request things
            $curlOptions[CURLOPT_POST] = TRUE;
            $curlOptions[CURLOPT_POSTFIELDS] = http_build_query( $params['url_params'] );

        } elseif ( 'GET' == $params['type'] ) { // get request things
            $curlOptions[CURLOPT_URL] .= '?' . http_build_query( $params['url_params'] );
        }

        // initialize curl
        $ch = curl_init();

        // set curl options
        curl_setopt_array( $ch, $curlOptions );

        // make call
        $apiResponse = curl_exec( $ch );
        if ( isset( $params['authorization'] ) ) { // we have headers to deal with
            // get size of header
            $headerSize = curl_getinfo( $ch, CURLINFO_HEADER_SIZE );

            // remove header from response so we are left with json body
            $apiResponseBody = substr( $apiResponse, $headerSize );

            // json decode response body
            $apiResponse = json_decode( $apiResponseBody, true );
        } else { // no headers response is json string
            // json decode response body
            $apiResponse = json_decode( $apiResponse, true );
        }

        // close curl
        curl_close( $ch );
        return array(
            'status' => isset( $apiResponse['status'] ) ? 'fail' : 'ok', // if status then there was an error
            'message' => isset( $apiResponse['message'] ) ? $apiResponse['message'] : '', // if message return it
            'api_data' => $apiResponse, // api response data
            'endpoint' => $curlOptions[CURLOPT_URL], // endpoint hit
            'url_params' => $params['url_params'] // url params sent with the request
        );
    }
}

include(__DIR__ . '/../config.php');
$thing = new eciTwitchApi(CLIENT_ID, CLIENT_SECRET);

if (isset($_GET['code']) && $_GET['code']) {
    $data = $thing->getTwitchAccessToken( $_GET['code'], REDIRECT_URI );
    echo '<pre>';
print_r($data);
    echo '</pre>';
} else {
    echo $thing->getLoginUrl(REDIRECT_URI);
}

I got a token.

Array
(
    [status] => ok
    [message] => 
    [api_data] => Array
        (
            [access_token] => REDACTED
            [refresh_token] => REDACTED
            [token_type] => bearer
        )
    [endpoint] => https://id.twitch.tv/oauth2/token
    [url_params] => Array
        (
            [client_id] => hozgh446gdilj5knsrsxxz8tahr3koz
            [client_secret] => REDACTED
            [code] => REDACTED
            [grant_type] => authorization_code
            [redirect_uri] => http://localhost:8000/
        )
)

I am unable to find an issue, unless you got a SSL/Cert error you didn’t catch.

Ran using php -S 127.0.0.1:8000 under php 8.0.6 (cli) built may 13th

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.