Id_token missing when using id.twitch.tv/oauth2/token with grant_type refresh_token

Howdy all! I’m running into some issues when trying to use a grant_type refresh_token to retrieve an id_token from id.twitch.tv . Is this not a supported use case? I get a successful response from the oauth2/token endpoint, but the body does not include an id_token. Here’s the code (node.js + request) and output:

let params = {
  client_id: client_id,
  client_secret: client_secret,
  refresh_token: encodeURI(refresh_token),
  grant_type: "refresh_token",
  scope: "openid",
  response_type: "id_token+code"  // not sure if required, or helpful??
}
let twitchTokenUrl = buildUrl("https://id.twitch.tv/oauth2/token", params)
console.log("posting: " + twitchTokenUrl)
request.post({
  url: twitchTokenUrl,
  json: true,
  headers: {'User-Agent': 'My-Cool-Appname'}
}, (err, reqRes, data) => {
  console.log("twitch token response")
  console.log(data)
})

yields =>

posting: https://id.twitch.tv/oauth2/token?client_id=R3D4CT3D&client_secret=R3D4CT3D&refresh_token=R3D4CT3D&grant_type=refresh_token&scope=openid&response_type=id_token
twitch token response
{ access_token: 'R3D4CT3D',
  expires_in: 13628,
  refresh_token: 'R3D4CT3D',
  scope: [ 'openid' ],
  token_type: 'bearer' }

On the other hand, this flow works great and does exactly what I want when I provide an authorization_code from an authorize redirect flow instead:

let params = {
  client_id: client_id,
  client_secret: client_secret,
  code: accessCode,  // retrieved from a twitch authorize redirect page
  grant_type: "authorization_code",
  redirect_uri: "https://my_cool_redirect.uri"
}
let twitchTokenUrl = buildUrl("https://id.twitch.tv/oauth2/token", params)
console.log("posting: " + twitchTokenUrl)
request.post({
  url: twitchTokenUrl,
  json: true,
  headers: {'User-Agent': 'My-Cool-Appname'}
}, (err, reqRes, data) => {
  if (err) return reject(err)
  console.log("got token from twitch:")
  console.log(data)
}

yields =>

posting: https://id.twitch.tv/oauth2/token?client_id=R3D4CT3D&client_secret=R3D4CT3D&code=R3D4CT3D&grant_type=authorization_code&redirect_uri=https://my_cool_redirect.uri
got token from twitch
{ access_token: 'R3D4CT3D',
  expires_in: 15797,
  id_token: 'eyJh.R3D4CT3D._84A',  // THIS is what I am missing when using grant_type refresh_token
  refresh_token: 'R3D4CT3D',
  scope: [ 'openid' ],
  token_type: 'bearer' }

However, I’d like to avoid the authorize / redirect loop if at all possible, and just use the refresh_token to get a new id_token once it has expired; else I’m not exactly sure what the refresh_token or access_token is good for.

As a side note: is there a good reason why the id_token exp claim is not congruent with the access_token expires_in field?

Thanks!

As the refresh docs page mentions: https://dev.twitch.tv/docs/authentication/#refreshing-access-tokens

(Note that app access tokens and ID tokens cannot be refreshed.)

Also the OIDC page itself: https://dev.twitch.tv/docs/authentication/getting-tokens-oidc/

  • exp – Expiration time (note that when the JWT ID tokens expire, they cannot be refreshed)

Huh, I missed those bullet points the first few times through, thanks.

It sounds like this isn’t going to change, so I’ll plan to switch to issuing a self-signed JWT with the access_token as a (edit: hashed) claim instead of having users carry their own Twitch-issued OIDC tokens as authorization passes with our services (unless this sounds dumb to you, and you feel kind enough to point out why :wink: )

For my own curiosity: is there some reason or spec that explains why 1) OIDC tokens are so short lived and 2) they cannot be refreshed? (i’m definitely not trying to start any arguments or anything, but here’s Eugenio Pace, founder & CEO of Auth0 claiming that–at least, in 2014–access_token’s and id_token’s are logically equivalent, and suggests the exact flow I was trying to accomplish here as a valid flow: https://stackoverflow.com/a/25695820/10453923 .) If this is incorrect, or outdated info, I’m curious as to why. Thanks in advance!

I can’t speak to why Twitch chose OIDC tokens to have the expiration times that they do, or as to why ID tokens can’t be refreshed.

From how I’m reading the OIDC specification providing a refresh token is optional, and when one is provided the main point they make is The Authorization Server MUST validate the Refresh Token, MUST verify that it was issued to the Client, and must verify that the Client successfully authenticated it has a Client Authentication method. and then goes on to say Upon successful validation of the Refresh Token, the response body is the Token Response of Section 3.1.3.3 except that it might not contain an id_token..

So the way Twitch is doing things is still going in line with the specification, as like you showed in your first post the authorization server does respond without error, it’s just that it doesn’t respond with an id token as that’s optional.

I don’t know enough about OIDC, and the various use cases for it, to know why it might be done this way, maybe Twitch staff or someone more experienced in OIDC can chime in.

1 Like

Well, for any twitch staff following along: I worked around this issue by not using the twitch id_tokens anymore: https://github.com/mtgatracker/mtgatracker-webtask/pull/7/files#diff-3c3d50f3c617d6af6483e5934320b99fR214

As it is, I don’t really see a point in id_token’s at all. Even as a “secondary” method of verifying the response from the original oauth request (i.e. against the jwk), there’s nothing that relates the access_token to the id_token, so it’s pretty much validating two separate pieces of info (rather than double-validating the info that matters, in this case, the access_token). Furthermore, without a way to refresh the id_token, the double-validation-but-not-really only works once.

Maybe this is just me not getting it, but… well, I don’t get it :slight_smile:

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