Skip to content

Authentication ​

You can log into emusks through three main ways:

  • with an auth_token cookie (recommended)
  • with a username and password
  • with a code Twitter emails you
  • or not log in at all, and read public data without an account with guest tokens or syndication.

Auth token login ​

The simplest and most reliable way to log in is with the auth_token cookie from a browser session where you're signed in to x.com.

js
import Emusks from "emusks";

const client = new Emusks();
await client.login("your_auth_token_here");

Pass an object instead when you also want a proxy, a different client or a different endpoint (see configuration):

js
await client.login({
  auth_token: "your_auth_token_here",
  proxy: "socks5h://user:pass@host:1080",
});

Finding your auth token ​

  1. Open x.com in your browser and log in
  2. Open Developer Tools (F12 or Cmd+Shift+I)
  3. Go to Application, then Cookies, then https://x.com
  4. Find the cookie named auth_token
  5. Copy its value

Username and password login ​

The password login runs Twitter's current Jetfuel login flow and handles two-factor codes, email codes and identifier challenges through a callback.

This is not reliable or recommended, since this may be changed at any time. Twitter's been throwing as many CAPTCHAs as they can at this flow.

Using a headful web browser ​

By default, since Twitter requires a browser to mint a Castle token (they reject tokens minted headless or outside a real browser with "We've temporarily limited your login"), emusks opens a real browser window for each login (castle: "browser").

js
const client = new Emusks();
await client.login({
  type: "password",
  username: "your_username",
  password: "your_password",
});

That needs Chrome or Chromium installed, and a browser window flashes briefly per token. Set EMUSKS_CHROME_PATH if Chrome isn't in a standard location.

Using your own Castle minter ​

This skips the browser and asks your own function for each token. It's recommended if you're using a Castle solver API and for production use.

js
await client.login({
  type: "password",
  username,
  password,
  getCastleToken: (action) => myMinter(action),
});

You can also use castle: "node", and emusks will try to mint tokens in a sandbox instead. This currently doesn't work. Learn more about how emusks deals with Castle.

Two-factor and email codes ​

When Twitter asks for a code, emusks calls onRequest and waits for it to return the value:

js
await client.login({
  type: "password",
  username: "your_username",
  password: "your_password",
  onRequest: async (type) => {
    if (type === "two_factor_code") return "123456";
    if (type === "email_code") return "654321";
  },
});

type is two_factor_code, email_code or alternate_identifier. The flow stops on server errors instead of resubmitting rejected credentials.

Passing email and phone up front lets emusks answer identifier challenges without asking you:

js
await client.login({
  type: "password",
  username: "your_username",
  password: "your_password",
  email: "[email protected]",
  phone: "+1234567890",
  onRequest: async (type) => {
    if (type === "two_factor_code") return "123456";
  },
});

Identity challenges ​

Before the password step, Twitter sometimes finds your account from the username or email you passed and asks you to confirm one identifier tied to it: Username, Email or Phone. onRequest gets that as an optional second argument, so you can answer the right one:

js
await client.login({
  type: "password",
  username: "your_username",
  password: "your_password",
  onRequest: async (type, context) => {
    if (type === "alternate_identifier") {
      if (context?.challengeType === "Username") return "your_handle";
      if (context?.challengeType === "Phone") return "+1234567890";
      if (context?.challengeType === "Email") return "[email protected]";
    }
    if (type === "email_code") return "654321";
  },
});

context is { challengeType, accountFound, accountFoundType }, where accountFound is the value Twitter matched and accountFoundType is "email" or "username". Callbacks that only take (type) keep working.

If you return nothing for alternate_identifier, emusks takes Twitter's "use password" bypass and goes on to the password step. That works for low-risk accounts. A flagged account may get the check again.

Email code login ​

type: "email_code" logs in without a password. Twitter emails a code to the account and emusks submits whatever onRequest("email_code") returns.

js
await client.login({
  type: "email_code",
  username: "your_handle_or_email",
  email: "[email protected]",
  onRequest: async (type, context) => {
    if (type === "email_code") return await readEmailCode();
    if (type === "alternate_identifier") {
      if (context?.challengeType === "Username") return "your_handle";
      if (context?.challengeType === "Phone") return "+1234567890";
      return "[email protected]";
    }
  },
});

Whether Twitter offers a password-free login depends on the account. When Twitter shows a password-or-code choice, emusks picks the code. If Twitter insists on a password, login throws email-code login is not available for this account. This mode only runs on the Jetfuel flow, so flow: "classic" throws.

Login options ​

OptionTypeDescription
auth_tokenstringThe auth_token cookie. A plain string argument to login() is treated as this.
typestring"password", "email_code", or "guest" (see guest tokens)
usernamestringYour handle or email. Required for password and email_code.
passwordstringYour password. Not needed for email_code.
emailstringAnswer for email identifier challenges
phonestringAnswer for phone identifier challenges
onRequestfunction(type, context?) callback for codes and identifier challenges
getCastleTokenfunctionYour own Castle token source, called once per login step
castlestring"browser" (default) or "node"
castleHeadlessbooleanRun the Castle browser headless. Default false; Twitter usually rejects headless tokens.
flowstring"jetfuel" (default) or "classic", the old onboarding flow that Twitter no longer accepts
timeoutnumberPer-request timeout of the login flow, in seconds
debugbooleanLog every login step to stderr
client, endpoint, transactionIds, proxySee configuration

Elevated access ​

Some sensitive actions, like reading certain settings, need a recently confirmed password. elevate() confirms it once and keeps the resulting cookies on the client:

js
await client.login("your_auth_token");
await client.elevate("your_password");

It throws invalid password when Twitter rejects the password. The elevated cookies stay on the client until you discard it.

Checking your session ​

js
const me = await client.account.viewer();
console.log(`logged in as @${me.username} with ${me.stats.followers.count} followers`);

You can get your client from client.user or from what login() returns as well.

Next steps ​

Head over to configuration to pick which client to emulate, set up proxies and handle errors.

not affiliated with X Corp.