Programattically Get User OAuth Token for Subscriber Notifications

Is there any Python sample code that uses Flask or the equivalent to create a minimal web server for use in getting the user OAuth Token?

I have Python bot that was able to get Subscriber Notifications before this change:

Requiring OAuth for Helix Twitch API Endpoints

I spent hours last night trying to figure out how to do this in Python, but got nowhere because eventually some part of Twitch wanted to contact a local web server to do something. I tried reading the Javascript sample code and adapting it, but it appeared to assume knowledge I do not have.

For normal oAuth the flow is

Step 1 of oAuth is send the user you want to get notifications for to Twitch in a Web Browser to accept of decline the connection of their Twitch account to your Application.
Step 2 they come back to your redirect with a code
Step 3 you exchange the code for an access token and a refresh token

Then you use the access token till it expires.
When it expires, you use the refresh token to get a new access token.

The last part with refreshing you can do that in code until the user disconnects your app, or some other criteria invalidates the refresh token.

So you need a webserver to start with to get the initial access and refresh tokens.

You bot can pull them from chat, chat doesn’t require any special authentication from the broadcaster so the Helix oAuth change makes no difference

But you must of been using the broadcasters access token before this change. All this change does is require all helix endpoints to use a token, but the subscriber endpoints are gated by the broadcasters Twitch oAuth token anyway. So the change shouldn’t of effected you

Barry, there seems to be a fundamental misunderstanding here. I created a user account for use by Python code to connect to Twitch and get a user ID. The user ID is required by the webhook code to subscribe to follower notifications:

    def subscribe(self):
        headers = {"Client-ID": self.client_id}
        data = {
            "hub.mode": "subscribe",
            "hub.topic": f"{self.twitch_api_base}/users/follows?first=1&to_id={self.user_id}",
            "hub.callback": self.public_uri,
            "hub.lease_seconds": str(24 * 60 * 60)
        }
        result = requests.post(self.webhooks_url, json=data, headers=headers)
        if result.status_code == requests.codes.accepted:
            self.logger.debug("Our subscription request was accepted by Twitch")
        else:
            self.logger.debug("Failed to subscribe to follower notifications")
            self.logger.debug(f"result.status_code = {result.status_code}")
            self.logger.debug(f"result.text = {result.text}")

The problem I am having, is that the user account can no longer get the user ID because of the new requirement to get an OAuth token. Is there some other way to programmatically get the user ID of a Twitch user? If so, I have not been able to find it.

Why not just manually get the user ID, you are probably wondering? Because then it isn’t automated, and the code cannot easily be shared and reused.

becuase

You wrote about subscriber notifications, and to get subscriber notifications via the API, Webhooks or PubSub requires a user token from the broadcaster. Yes a chatbot can read them from Chat anonomously or logged in as a User account, but that is unclear where you are getting the notifications from.

Yes, either use the token that you are using for the bot to login to chat with, or use a server to server token aka App access token aka “client credentials”

I am using this script to test things:

curl --data "client_id=smwpepmr7s3yt9gqwlz628b2e4tww8" \
     --data "client_secret=<client_secret>" \
     --data "grant_type=client_credentials" \
     --data "scope=openid" \
     https://id.twitch.tv/oauth2/token

The output looks like this:

{
    "access_token":"REMOVED ACCESS TOKEN",
    "expires_in":5045536,
    "scope":["openid"],
    "token_type":"bearer"
}

However, when I try to use the “access_token” to log in, it doesn’t work. Should I be using a different “scope”? Am I using the “access_token” incorrectly?

To login? Login to what?

It doesn’t make sense to apply scopes to an app access token.

App Access AKA server to server, doesn’t represent a user, so the scopes don’t grant you access to anything. You’ll only be able to get public data.

Are you always this helpful?

I used to be able to subscribe to follower notifications. Now I get this error message:

2020-06-09 14:47:02.082340: twitch_follow_server.py:   143: DEBUG  : Failed to subscribe to follower notifications
2020-06-09 14:47:02.082466: twitch_follow_server.py:   144: DEBUG  : result.status_code = 401
2020-06-09 14:47:02.082545: twitch_follow_server.py:   145: DEBUG  : result.text = {"error":"Unauthorized","status":401,"message":"OAuth token is missing"}

Any idea how I can get that OAuth token? Or are you just going to keep pasting links to the same unhelpful documentation?

Lets start over in that case.

Your topic says “subscriber notifications”
Your last post talks about “logging in”
And now you are after follower notifications.

That’s three different things.

The last is public data and works with any kind token
Logging in, assuming you mean chat will require a user token
And the first is subscription data, which requires a user token from the broadcaster.

That’s 2 different token types across three differrent “user types” (where one user is not a user but a server)

I don’t know what you are actually trying to do.

This error states you have not included an oAuth token.

I’m gonna guess you are using the followers webhook?

In that case you probably need an App Access Token.

But can you confirm what you are actually trying to do. What ends points you are calling or intending to call.

Since I don’t understand your questions, I’ll just post some more actual code. This is the class I used to subscribe to follower notifications:

class TwitchWebhookInterface(object):
    def __init__(self, logger, follow_server):
        self.logger = logger
        self.follow_server = follow_server
        add_configuration(self)
        self.webhooks_url = f"{self.twitch_api_base}/webhooks/hub"
        self.interface = TwitchChannelInterface(self.channel, self.client_id)
        self.user_id = self.interface.get_id()

    def subscribe(self):
        headers = {"Client-ID": self.client_id}
        data = {
            "hub.mode": "subscribe",
            "hub.topic": f"{self.twitch_api_base}/users/follows?first=1&to_id={self.user_id}",
            "hub.callback": self.public_uri,
            "hub.lease_seconds": str(24 * 60 * 60)
        }
        result = requests.post(self.webhooks_url, json=data, headers=headers)
        if result.status_code == requests.codes.accepted:
            self.logger.debug("Our subscription request was accepted by Twitch")
        else:
            self.logger.debug("Failed to subscribe to follower notifications")
            self.logger.debug(f"result.status_code = {result.status_code}")
            self.logger.debug(f"result.text = {result.text}")

The part that no longer works is the part that gets the “client_id”:

    self.interface = TwitchChannelInterface(self.channel, self.client_id)
    self.user_id = self.interface.get_id()

I can’t get the “user_id” anymore, because Twitch wants my bot to login using OAuth. This is the part where you have been singularly unhelpful. How do I programmatically log a bot into Twitch? My bot isn’t a human sitting at a computer using a web browser. My bot is a Python script. Previously, I used code like this to get the “user_id”:

def get_id(self):
    api_url = f"{self.twitch_api_base}/users"
    headers = {"Client-ID": self.client_id}
    payload = {"login": self.channel}
    result = requests.get(api_url, params=payload, headers=headers)
    if result.status_code == requests.codes.ok:
        self.data = result.json()
        self.channel_id = self.data['data'][0]['id']
    return self.channel_id

This worked when no OAuth was required. This no longer works. So, once again, how do I get that “user_id” (AKA “channel_id”)? Can you think of minimal changes to this specific method that might fix the problem?

So you are using Twitch Webhooks.

You need to generate and use a “App Access Token” also called “OAuth client credentials flow”

That is documented here

You use the ClientID and ClientSecret to generate a server to server app access token, and specify that with your clientID in the headers to the webhook creation request.

You can use the App Access Token for this call also

becomes

headers = {"Client-ID": self.client_id, "Authorization": "Bearer " + yourGenerateToken}

You shouldn’t generate a token for every request, an app access token is valid for around 60 days.

POST https://id.twitch.tv/oauth2/token
    ?client_id=<your client ID>
    &client_secret=<your client secret>
    &grant_type=client_credentials

No scopes needed. What you did here:

Got you a working token, you just don’t need any scopes

So, now to the final problem. This method no longer works:

def subscribe(self):
    headers = {"Client-ID": self.client_id}
    data = {
        "hub.mode": "subscribe",
        "hub.topic": f"{self.twitch_api_base}/users/follows?first=1&to_id={self.user_id}",
        "hub.callback": self.public_uri,
        "hub.lease_seconds": str(24 * 60 * 60)
    }
    result = requests.post(self.webhooks_url, json=data, headers=headers)
    if result.status_code == requests.codes.accepted:
        self.logger.debug("Our subscription request was accepted by Twitch")
    else:
        self.logger.debug("Failed to subscribe to follower notifications")
        self.logger.debug(f"result.status_code = {result.status_code}")
        self.logger.debug(f"result.text = {result.text}")

The error message is

2020-06-09 16:41:23.927247: twitch_follow_server.py:   161: DEBUG  : Failed to subscribe to follower notifications
2020-06-09 16:41:23.927360: twitch_follow_server.py:   162: DEBUG  : result.status_code = 401
2020-06-09 16:41:23.927427: twitch_follow_server.py:   163: DEBUG  : result.text = {"error":"Unauthorized","status":401,"message":"OAuth token is missing"}

Now, if you’ve been paying close attention, you’ll notice this is my original problem and the title of the question.

I don’t know how to get the OAuth token here.

You didn’t update the headers to include your access token

I did do that, but order matters. If you put “Authorization” before “Client-ID”, it seems to care. Which is really bizarre, considering that “headers” is a dict. Anyway, here is github repo with working code for the next poor soul who has similar problems:

Also, I don’t recommend the following syntax:

"String" + variable

It’s not idiomatic Python, and produces fragile code. For an example of why it’s bad, see this:

>>> "String" + 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects

https://github.com/MountainRiderAK/TwitchChatBot/commit/0c68d2a7e55408938b24736f278dfd1e59c51be8 Line 52 contains your Client Secret.

In fact this commit has your client secret in a few places.

Your Client Secret is like a password. It shouldn’t be released publically.

You should also, where possible generate an use an Access Token till it expires and not generate one for each request to the API. But at a skim you seem to only do this when making a new webhook subscription so that’s “ok”, just frowned upon

Additionally: having looked at your code, you could of used {self.tim_token} as this would of been a valid oAuth token for the API, you’d just have to remove oauth: from the start. A token that allows a user to login to twitch chat, could of been used to set up the follower webhook.

“premature optimization is the root of all evil (or at least most of it) in programming.” Donald Knuth

I feel lucky just to have this working, and I already changed my Client Secret. Take a deep breath. Someone would have to stumble across this post and then go to my repo and then they would have a stale Client Secret. I think we’re safe. But thanks for keeping an eye out.

lol @ "you could [have] used {self.tim_token}". bruh, that was one of the first things I tried. You really don’t understand how bad the documentation and support for Twitch is if you think I didn’t literally spend hours trying everything I could think of before finally breaking down and asking a question here.

I predict the error there was that you got “client-id and token don’t match” as the token looks like it would of been generated via the TMI Token Generator mentioned in example_config.json, so you would of had to have used TwitchApps Client-ID.

The general advice is to not use a third party to generate tokens.

So right now your code uses a token from Service A to run the bot, and a token from Service B to run webhooks. So you are managing Two Sets of tokens from two Different providers, where the first set you can’t auto refresh as it was generated via a third party. (and you don’t have the refresh token or client secret to automate refreshing of the token at bot boot)

Side note: TwitchApps is probably still generating tokens that last forever, but those tokens will get killed in the not to distant future.

I covered that here.

But I didn’t know you were using a Token from a Third Party Service, which introduces it’s own problems.

I wish to God in Heaven I knew what all that jargon you are spewing meant.

And I don’t know how to get a TMI_TOKEN from anything other than a Third Party Service because I never found anything in the Twitch documentation that explained how to get one in the correct way. Maybe instead of just pointing out the flaws (which you are really good at, you’ll make a great parent), you might also provide links to the correct way of doing things.

Finally, I can’t stress this enough: Just getting this working took enormous effort. Far more effort than should have been necessary. So if you’re not too busy, maybe you could spend some time improving the documentation. Or you could keep doing things the way you’ve been doing them. Seems to be the current trend.

Right now, I have working code, and that trumps everything. Also, it’s on github, so anyone can fork it, fix it, and submit a pull request. You’re focused on the wrong problems.

I don’t write the documentation and I cannot make modifications. I am not Twitch staff. I’m just another developer like you.

Like 15, mentions the third party token generator.

I already did this numerous times in this thread by linking to the oAuth documentation that Twitch has. Given that oAuth on Twitch works the same as many other services that implement oAuth. (Except Twitter that uses a older version of oAuth).

The docs cover how to get tokens, the correct way to do something depends vastly on what you application is, how it runs, what permanent storage is available if any, is it stateful or stateless, what does it run on, what language is it to be built in. But my initial answers were wrong because you asked about Subscriber Notifications to start with. And the oAuth change for Helix requires people using subscriber notifications to make no changes, since you need the broadcasters oAuth.

And as I already said elsewhere, you started asking about authentication Subscriber notifications, when in reality you were talking about authentication for followers webhooks. Which are two totally different things.

Basically it’s all got a bit of a mess due to wrong terminology from the get go.

Explain it like I’m 5-years-old: Where in this link do I find the thing that replaces TIM_TOKEN:

I’ve read it several times (maybe several dozen times by now) and I don’t understand it. I’m trying to remember when I last saw documentation this bad, but it was probably back in the 1990s, and I don’t remember the specific product.

Also, I don’t remember referring to you as anything other than Twitch support. If I called you staff, that is my fault, and I apologize. If you are unable to modify the documentation, then who in the world is suppose to improve it?

1 Like

You have to go to this link:

https://id.twitch.tv/oauth2/authorize
    ?client_id=<your client ID>
    &redirect_uri=<your registered redirect URI>
    &response_type=<type>
    &scope=<space-separated list of scopes>

but instead fill out the information. This will bring your to a prompt to click “allow” that gives you, the programmer, permission to get the information from their account. So, for example, if I wanted to get permission to see a users subscriptions, I need them to give me access. So I might send them to this link
https://id.twitch.tv/oauth2/authorize?client_id=123456789&redirect_uri=localhost:3000&response_type=token&scope=channel_subscriptions

When they click that, they log into their twitch account and they can see what permissions are being requested. If they’re okay with you seeing it, they click “Allow” and it automatically redirects them to your registered URL with information added to the end of it. The redirection will be something like

localhost:3000#access_token=aSeriesOfRandomNumbersAndLetters&scope=channel_subscriptions&token_type=bearer

Now you take the access_token and you can use that to follow to documentation on accessing the users subscriptions with WebSockets.