Skip to content
twitch.apiv2.0

Authentication

Managing users

Everything twitch.auth does with user tokens once you have them: register, look up, refresh, validate, revoke and remove, and what happens when a token dies.

A user, for the client, is a user token it has been given: an access token, usually a refresh token, the scopes and the owner. twitch.auth is where you register them, inspect them and remove them. The flows (authorization code, device code) register the user for you. This page is for everything else.

import { Twitch } from "@ix-xs/twitch.api";
const twitch = new Twitch({
clientId: process.env.TWITCH_CLIENT_ID!,
clientSecret: process.env.TWITCH_CLIENT_SECRET!,
});
const bot = await twitch.auth.addUser({ accessToken, refreshToken });
console.log(bot.userId, bot.login, bot.scopes);
console.log(twitch.auth.hasUser(bot.userId)); // true
console.log(twitch.auth.users.length); // 1
await twitch.auth.removeUser(bot.userId, { revoke: true });

twitch.auth.addUser(input) registers a user token and resolves with the resulting UserToken. Only accessToken is mandatory.

FieldTypeMeaning
accessTokenstringThe access token. Required.
refreshTokenstringThe refresh token. Without it the access token cannot be renewed.
userIdstringOwner of the token. Looked up from Twitch when left out.
loginstringLogin of the owner.
scopesstring[]Scopes of the token. Looked up from Twitch when left out.
expiresAtnumber or DateWhen the access token expires: epoch milliseconds or a Date.
expiresInnumberLifetime in seconds, as Twitch returns it. Used when expiresAt is absent.

What happens depends on what you give.

import type { UserToken } from "@ix-xs/twitch.api";
declare const response: { access_token: string; refresh_token: string; expires_in: number };
declare const saved: UserToken;
// An access token alone. Twitch is asked who owns it, with which scopes, until when.
await twitch.auth.addUser({ accessToken });
// With a refresh token, so that the client can renew it.
await twitch.auth.addUser({ accessToken, refreshToken });
// The answer of Twitch's token endpoint, field by field.
await twitch.auth.addUser({
accessToken: response.access_token,
refreshToken: response.refresh_token,
expiresIn: response.expires_in,
});
// A token you saved earlier, owner and scopes included. No request is made.
await twitch.auth.addUser(saved);
// The same, written out.
await twitch.auth.addUser({
userId: "141981764",
login: "twitchdev",
accessToken,
refreshToken,
scopes: ["user:read:chat", "user:write:chat"],
expiresAt: new Date("2026-10-12T18:00:00Z"),
});

The rules behind those forms:

  • When userId or scopes is missing, the client validates the token with Twitch and takes the owner, the login and the scopes from the answer, and the expiry too if you gave none.
  • When that validation says the access token is dead and you gave a refresh token, the token is refreshed on the spot. The UserToken you get back holds the new access token and the new refresh token.
  • When you give both userId and scopes, the token is trusted as it is and nothing is sent. Scope checks then rely on the list you gave, so pass the real one.
  • A token with no known expiry is not refreshed ahead of time. It is refreshed when Twitch rejects it, and the hourly validation fills in the expiry.
  • Registering a user who is already registered replaces their token.
  • The token is saved in your store, if you configured one.

addUser fails in these cases:

ErrorWhen
TwitchApiError with status 401The access token is invalid and there is no refresh token to renew it.
TwitchApiErrorThe refresh attempted on the spot was refused.
TwitchAuthError: This is an app access token, not a user access token.The token belongs to no user.
TwitchAuthError: This token was issued to another application (client ID ...), so it cannot be used with client ID ....The token was granted to a different client ID than the one the client was built with.
TwitchNetworkError or TwitchTimeoutErrorTwitch could not be reached to validate the token.

All of them come from the validation step. A token registered with both userId and scopes skips that step, and a token from another application is then only found out when Twitch refuses the first call.

Three synchronous members read the users loaded in the client.

const everyone = twitch.auth.users; // UserToken[]
const token = twitch.auth.getUser("141981764"); // UserToken | undefined
const known = twitch.auth.hasUser("141981764"); // boolean
if (token && !token.scopes.includes("user:write:chat")) {
console.log(`${token.login} has to sign in again to let the bot talk.`);
}

They only see what is in memory. With a token store, users are loaded at the first request or the first twitch.auth.restore(), not when the client is built. See Storing tokens.

To know what a user allowed without holding their token, an application can ask Twitch with users.authorization, which uses the app token.

You rarely need these three: the client refreshes and validates on its own. They exist for the moments when you want to decide.

twitch.auth.refresh(userId) renews the access token now and resolves with the new UserToken. Calls made at the same moment for the same user share one request. The new token is saved in the store and announced on the tokenRefresh event. The request to Twitch is sent once and never repeated, because a refresh token may be single use.

With a token store, refresh reads the store first. When the token saved there differs from the one in memory, another process already renewed it: refresh resolves with that token and sends nothing. Storing tokens explains how processes share a store.

const fresh = await twitch.auth.refresh(userId);
console.log(new Date(fresh.expiresAt ?? 0));

twitch.auth.validate(token) asks Twitch about any access token, registered or not, and resolves with a TokenInfo. It throws a TwitchApiError of status 401 when the token is not valid. The request is retried after a network failure or a 5xx, like the one for the app token.

const info = await twitch.auth.validate(accessToken);
console.log(info.userId, info.login); // null and null for an app token
console.log(info.scopes, info.clientId);
console.log(`${info.expiresIn} seconds left`);
FieldTypeHolds
clientIdstringClient ID the token was issued to.
userIdstring or nullOwner of the token. null for an app access token.
loginstring or nullLogin of the owner. null for an app access token.
scopesstring[]Scopes granted to the token.
expiresInnumberSeconds left. 0 means the token never expires.
expiresAtnumber, optionalExpiry in epoch milliseconds. Absent when the token never expires.

twitch.auth.revoke(token) invalidates an access token at Twitch. It does not touch the users of the client: to revoke and forget someone in one call, use removeUser with revoke.

twitch.auth.removeUser(userId) forgets a user: the token leaves the memory of the client and your store. With { revoke: true } the access token is also revoked at Twitch, which is what a “log out” or “disconnect” button should do.

await twitch.auth.removeUser(userId, { revoke: true });
  • Removing someone who is not registered does nothing.
  • A failure of the revocation is ignored: the user is removed either way.
  • tokenInvalid does not fire. That event is for tokens that died, not for the ones you removed.
  • A refresh that was in flight for that user fails with a TwitchAuthError instead of bringing the user back.

Twitch requires applications that keep users signed in to validate their tokens every hour, so that a user who disconnects your application is noticed within the hour. The client does it for you.

  • The timer starts when the first user is registered or restored, and stops when the last one is removed or when you call twitch.close().
  • It never keeps the process alive.
  • Each run is twitch.auth.validateAll(): every loaded token is validated, its scopes and its expiry are updated from Twitch’s answer.
  • A token Twitch calls invalid is refreshed when it has a refresh token, and dropped when it has none.
  • A run that cannot reach Twitch changes nothing. The next one tries again.

Turn the timer off with validateTokens: false when you want to run the validation on your own schedule, for example on a platform with no long-lived process.

import { Twitch } from "@ix-xs/twitch.api";
const twitch = new Twitch({ clientId, clientSecret, validateTokens: false });
// Later, from your own scheduler:
await twitch.auth.validateAll();

A token dies when its user disconnects your application in their Twitch settings or changes their password, when a refresh token of a public client stays unused for 30 days, or when a token with no refresh token expires. The client finds out in one of three places:

WhereConditionError
A refreshTwitch answers that the refresh token is invalid, and the store holds no newer oneTwitch rejected the refresh token of user 123: ... The user has to authorize the app again.
A call answered 401The token is dead and has no refresh tokenThe access token of user 123 is no longer valid and there is no refresh token to renew it. The user has to authorize the app again.
The hourly validationThe token is dead and has no refresh tokenThe token of user 123 is no longer valid.

Each time, the same things happen: the user is removed from the client, the token is deleted from the store, and tokenInvalid fires with the user ID and a TwitchAuthError. In the first two cases the call that ran into the dead token rejects with that same error, whose userId field names the user.

declare function askToSignInAgain(userId: string, reason: string): void;
twitch.on("tokenInvalid", (userId, error) => {
askToSignInAgain(userId, error.message);
});

After that, a call for this user fails with No token is registered for user 123. Add it with twitch.auth.addUser(). until they sign in again. Twitch also revokes the event subscriptions that depended on the authorization, which the listeners report through onRevocation: see Managing subscriptions.

A refresh that fails for another reason (the network, a 5xx, an error that is not about the refresh token) is thrown as it is and the user is kept, so an outage at Twitch does not sign anyone out.

Register as many users as you need. Each has their own token, their own scopes and their own rate limit bucket.

declare const botTokens: { accessToken: string; refreshToken: string };
const broadcaster = await twitch.auth.addUser({ accessToken, refreshToken });
const bot = await twitch.auth.addUser(botTokens);
// The broadcaster_id names a registered user: their token is used.
await twitch.polls.create({
broadcaster_id: broadcaster.userId,
title: "Next game?",
choices: ["Hades", "Celeste"],
duration: 120,
});
// Nothing in this call says who sends the message: name the bot.
await twitch.chat.send(
{ broadcaster_id: broadcaster.userId, message: "The poll is open" },
{ as: bot.userId },
);

With one user registered, calls that need a user token use that user. With several, the client reads the request to find whose token fits. When it cannot tell, the call fails with a TwitchAuthError that asks you to choose with { as: userId } or twitch.as(userId). Without the { as: bot.userId } above, the message would be:

POST /chat/messages does not say who is acting and several users are registered. Set "sender_id", or choose a user with { as: userId } or twitch.as(userId).

The exact order is in Acting as a user.

To do the same thing for everyone, loop over the users and name each one:

for (const user of twitch.auth.users) {
const live = await twitch.streams.followed({}, { as: user.userId }).take(5);
console.log(user.login, live.map((stream) => stream.user_name));
}