Long-Lived Desktop Chat Application OAuth Token Secure Storage

Howdy,

I’m building a long-lived (e.g. during an entire broadcaster’s stream) desktop chatbot application.
In my case, I do not want to interrupt the broadcaster in the middle of their stream to refresh their (user) access token.

If I use the code grant flow, this gives me a refresh token that can be used in the background to do this. However, using that flow requires storing the client secret on their computer.

On the other hand, I could use the implicit flow. This doesn’t require the client secret, however the lifetime of the token is really really long, roughly 60 days.

Ideally I could use PKCE so I don’t have to store anything at all, but this isn’t yet supported (vote at Add PKCE Support to the OAuth2.0 Authorization Code Flow – Twitch UserVoice)

So, I’m curious what other devs are doing to solve this problem?

I’ve thought to use the OS vault, realistically I’m only targeting Windows since this app also talks with OBS (and the other distros of OBS don’t get much love).

Otherwise, it becomes a bit of a chicken-egg problem of hashing the secret, but what hashing the secret hash, and hashing that…Or, I ask the user to install 3rd party vault software which doesn’t sound so great…Or, I need to spin up a web service somewhere that uses HashiCorp Vault and then let the user get their secrets from there (not willing to do a cloud deployment of anything atm, for cost reasons)…All this, as I understand it, to protect against malicious software hacking their PC and stealing those secrets. The impact wouldn’t be great, considering the token has channel:moderate scope…So, yucky.

Desktop app talks to me/my server,
Desktop app will use another method to secure connections from it and my server.
My server hold all the tokens and refresh tokens

Or

Use implicit auth, when the token dies you’ll ask the streamer to refresh and that’ll be fully transparent, with force verify off, as long as the streamer is still logged in to Twitch.
So app invokes chrome to open the oAuth loop
That auto completes since already granted and no force verify
Then the key is auto passed back to your app via either a local server in the app (that then auto closes the tab) or via someproctol://words (which is harder to auto close the tab)
This is mostly transparent.

I usually use the former, devise a way for the desktop app to secure connect to my server.
Then link that “account” with a Twitch Account in my server.
(My server also handles sending app updates, so I have a server anyway, I know some people use GitHub for releases so they don’t already have a server)

Generally I feel most people use implicit auth.
For the 60 days token

Then if there is less than, say, a week left on the token you can show an alert to the streamer that it’s time to reauth and they can do that pre/post a stream since the app shows an alert telling them it’s time to refresh.

'Course not much you can do when a token (and refresh token if you have one) are both killed due to Twitch password reset or another reason disconnecting your App and the User from each other

As to PKCE, to use an example, EDMC (a desktop app for Elite Dangerous), quite often the refresh token is dead (irrc FDEV only has a short expire on it’s refresh and Twitch doesn’t), so at app start, it prompts the oAuth flow in the web browser, which is disruptive anyway. Definelty would be nice though!

TLDR: My app connects to my server via custom accounts system
My server links that account with a Twitch account
My server manages token management, caching, eventsub relay, so on and so forth.

1 Like

Thanks for sharing!
Kinda what I was expecting, haha!

Seems like for the purely desktop app (no remote server deployment available), no-matter-what something secret gets stored – be it code grant with client secret & refresh token, or implicit with a long-lived token – something will be stored on the broadcaster’s PC.

It kinda makes sense a lot of folks would drift towards implicit because only one “secret” needs to be stored (the long-lived token), and (in the case of Twitch / against oAuth recommendations) is long-lasting.

Attack-vector-wise, compromising a secret or a long-lived token could have the same consequences, though at that point an attacker would have access to the broadcaster’s PC and could do lots of other damage. So unless either can be stored securely (like the remote server account you mentioned), I guess one would be indifferent to either option and go for the ‘simpler’ one (though in my case, it’s easier to use the code grant since Spring Security doesn’t support the implicit flow).

Only hesitation I have with implicit is that it’s not recommended wider-speaking anymore so I expect Twitch to eventually kill it at some point?

if the “Secret” in this case is the users own token.

Then it is perfeclty fine to store the users own token, in the desktop app, since it’s the users token.

For implicit auth you wouldn’t be storing anything “secret” clientSide.

And like you say

If they “won” the users PC, they can jsut grab the first party token, that is being used to talk to the Twitch website (via chrome) rather than looking for some misc program like yours. (Easier to go after a known attack vector/likely installed program than looking for misc apps like what we make)

Yeah I read that somewhere else recently. I don’t think there are plans to 'nix it.

And if they do, they are probably gonna replace it with PKCE like the linked uservoice you linked already. or (hopefully) follow whatever the RFC recommends.

1 Like

Hahah, I love security. :rofl:

Very good point on this – for most large-scale stuff, I’d expect targeting a bigger surface to be more profitable than some rando’s app. Doesn’t prevent researching/targeting a high-profile person, but that’d be a concern regardless of mitigation measures and some other app could be a weaker link (esp if file access is required anyway).

I was assuming folks would probably store the access token since it lives 60 days and (afik) doesn’t die when you request a new one? (unless there’s docs somewhere on invalidating an existing token, rather than hoping the user disconnects the app from their account – if I validate the token at https://id.twitch.tv/oauth2/validate then both my old and new token are still valid; I don’t get 401 unauthorized)

Thanks so much for your discussion like this, on a Sunday especially!

Revoke is documented here:

Twitch will let a given client generate either 25 or 50 (I forget which offhand) Tokens per user.

So when you make the 26th it will kill the first token for sure (by age).

Otherwise yeah, Twitch lets you have more than one active token. Since a given token could have a different group of scopes applied to it so.

So you could have

  • token a with chat scopes only
  • token b with just “subscriber read” scopes
    etc and so forth

No problem at all!

Also as a side note, I like to do “alternative auth” in the desktop app, and then my app uses a socket with my server, and then my server does all the grunt work of getting all the data into one feed to send to the app. 'course this use case will vary depending on what you app does. but just a side note that occured to me!

1 Like

Thanks! Can’t believe I somehow missed it, I’ve visited that page quite a lot and it’s right between the two sections I frequent most! :woozy_face:

Have considered this, though since I want to both chat and do chat commands (mute, ban, etc) I’ve not yet taken the effort to separate them or test that I don’t need both to e.g. mute an user.

Haha, that’s actually already my plan! Would probably go serverless and nosql too, to cut down on cost / handle spikes. It’s a monolith atm, but it shouldn’t need much refactoring esp since I use Spring Integration and there’s good adapter support there for different event/pub-sub tech stacks.

But for now not sure if folks will want it since most of the actions are desktop-side (esp communicating with OBS).

Anyway, thanks a lot! Hopefully other folks also find this discussion helpful thinking through these things, too.

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