API v5: Valid OAuth token resulting in "invalid token" error

Hi there, I’m writing an application in Node and I’m running into a bit of a puzzler. I’ve been able to successfully get an app access token from the API with no problems, but when I try to use that token to get a list of subscribers for the client, I’m met with an invalid token error. I’ve checked the token at the root url and it returns that it’s valid and has the correct scope (channel_subscriptions).

Even more puzzling is that my request for the subscribers list isn’t actually returning an error (the error object returns null). The response object includes a 401 Unauthorized status and the returned body is a bunch of garbled junk (see image).

Anyone have any ideas? I can paste the code I’m using as well upon request.

To note, I’ve also ensured to pass headers requesting the v5 API and Client-ID along with the Authorization, even though they’re not strictly necessary from what I understand.

Edit: Fixed a typo.

Kind of difficult without the code indeed. One thing people have missed in the past is that the token must be for the channel whose subscriber list you’re trying to request.

GET /kraken/channels/<channel ID>/subscriptions HTTP/1.1
Host: api.twitch.tv
Accept: application/vnd.twitchtv.v5+json
Authorization: OAuth <token of the user whose channel ID is above with channel_subscriptions>
Client-Id: xxx

The API version 5 header is always necessary (until February when it becomes default), client ID is only necessary when no valid oauth is present, so it’s good to send in case the oauth token is invalidated/revoked in order to get the correct response (401 instead of 400).

Thanks for the reply. I’ve included the code below, minus some fluff like access token caching and my actual client ID/secret.

var urlPrefix = 'https://api.twitch.tv/kraken/',
	request = require( 'request' ),
	kraken = request.defaults( {
		baseUrl: urlPrefix,
	 	headers: {
			'Accept': 'application/vnd.twitchtv.v5+json',
			'Client-ID': CLIENT_ID
	} ),
	twitch = {};

twitch._authenticate = function ( scope, force = false ) {
	return new Promise( function ( resolve, reject ) {
		kraken.post( {
			uri: '/oauth2/token',
			json: true,
			body: {
				'client_id': CLIENT_ID,
				'client_secret': CLIENT_SECRET,
				'grant_type': 'client_credentials',
				'scope': scope.join( ' ' )
		}, function ( err, res, body ) {
			if ( err ) {
				reject( err );
			} else {
				resolve( body.access_token );
		} );
	} );

twitch._getSubscriberList = function ( accessToken ) {
	kraken.get( {
		uri: '/channels/' + CHANNEL_ID + '/subscriptions',
		headers: {
			'Authorization': 'OAuth ' + accessToken
	}, function ( error, res, body ) {
		console.log( error ); // null
		console.log( res ); // response object which shows correct request headers
                            // and invalid token error
	} );

twitch._authenticate( ['channel_subscriptions'] ).then( token => {
		twitch._getSubscriberList( token );
} );

You mentioned something about making sure the token is for the correct channel. The Client ID & Secret I used to request the token are the ones Twitch provided when I registered the application, which was done on the same account for which I’m requesting the subscription list. So hopefully that should mean that the token corresponds to that account? Very new to v5 here though.

I also tested virtually the same code as twitch._getSubscriptionList with the Follower list endpoint since it doesn’t require authorization. That one worked perfectly fine, so I’m fairly confident in the code I’m using for the request.

Thanks in advance for any light you can help shed on the matter!

Nevermind the below, only applies to user access tokens.

The token will correspond to the account that authorized the application:

You can check whose token it is by requesting the root endpoint with the token:

To note, I have not set up an endpoint for user authorization via a dialog or anything; I simply snatched the Client ID and Secret from the registration page since I don’t plan on distributing this application. Could that be relevant? Checking the root endpoint with the token doesn’t provide a User ID, but it does provide my Username.

That’s a difference between v3 and v5, user_id field was added to the root endpoint. Although if the username does match what you’re expecting, it should work. As for the garbage in body, it’s probably gzipped and wasn’t decompressed by Request/node. Seems to happen sometimes with the 401 response.

As far as I can tell though, your app should work…

1 Like

I might have found the culprit, thanks to your post about user access tokens. I think my original understanding of what type of token I needed was incorrect. I’ll try to summarize my new understanding of the tokens and see if I’m mistaken or if this is what was wrong.

App access tokens are for server-to-server requests for public data not specific to a user.

Client access tokens are for server-or-client-to-server requests for sensitive data specific to a user.

Thus, for subscription data, I probably needed a client access token all along! Does this sound correct? And if so, what good are app access tokens really if they only allow access to endpoints that don’t even need authorization?

1 Like

That was indeed the culprit! Things are looking great now, thanks for your assistance. I’m still unsure on exactly when app access tokens should be used over user access tokens, but at least the user access token appears to be working now.

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