Skip to content

SteamAuthenticator

cbyte edited this page Jul 19, 2026 · 1 revision

SteamAuthenticator

steam.guard.SteamAuthenticator manages Steam Guard 2FA on an account: add a new mobile authenticator, generate TOTP codes, sign trade confirmations, remove an authenticator, and generate emergency backup codes.

The authenticator is bound to an account either via a MobileWebAuth session (WebAPI path — full feature set) or via a logged-in SteamClient (unified-messages path — everything except remove(), which Valve disabled on that channel).

Constructor

SteamAuthenticator(secrets=None, backend=None)
  • secrets — dict of authenticator secrets, if you already have them. If omitted, you'll get them from .add().
  • backend — a MobileWebAuth or SteamClient instance, logged in. Required for any network-touching operation. Optional for code-only use — you can hold a SteamAuthenticator with just secrets and call .get_code().

Common flow: bind a new authenticator

import json
import steam.webauth as wa
from steam.guard import SteamAuthenticator

# Step 1: log in with MobileWebAuth (needs your Steam account password).
mobile = wa.MobileWebAuth('steamuser')
mobile.cli_login()

# Step 2: instantiate the authenticator.
sa = SteamAuthenticator(backend=mobile)

# Step 3: request activation. Steam sends an SMS code to the account's phone.
sa.add()

# Step 4: SAVE THE SECRETS. If you lose them here you lose account access.
json.dump(sa.secrets, open('./mysecrets.json', 'w'))

# Step 5: complete the bind by echoing the SMS code back.
sa.finalize('SMS_CODE_FROM_TEXT')

# Step 6: use it.
print(sa.get_code())     # current 2FA code

Reusing an existing authenticator

Once you have secrets on disk, no backend is needed for pure code generation:

secrets = json.load(open('./mysecrets.json'))

sa = SteamAuthenticator(secrets)
print(sa.get_code())

Method reference

.add()

Provision a new authenticator. Populates .secrets with a dict containing shared_secret, identity_secret, revocation_code, serial_number, and other bookkeeping. Triggers Steam to send an SMS code to the account's phone.

Raises SteamAuthenticatorError if:

  • The account has no verified phone number (has_phone_number() first, then add_phone_number(...) if needed — see below).
  • Steam rejects the request (resp['status'] != EResult.OK).

Save the secrets before calling finalize(). They can't be recovered afterwards.

Adding a new authenticator invalidates any older one on the same account.

.finalize(activation_code)

Complete the bind using the SMS code Steam sent during .add(). Auto-retries up to 5 times with a +30s time drift adjustment when Steam returns want_more=True, since a large time offset between your clock and Steam's causes early rejections.

Raises SteamAuthenticatorError on final failure.

.get_code(timestamp=None)

Generate a Steam 2FA code (5 characters, Steam's custom alphabet). Uses .get_time() internally, which aligns with Steam's server time if align_time_every is set.

Signature: get_code(timestamp=None) -> str

.remove(revocation_code=None)

Deactivate the authenticator. revocation_code defaults to self.secrets['revocation_code'] — the R-prefixed code Steam returned in .add().

Only works via MobileWebAuth. Valve disabled this endpoint over the unified-messages channel — trying it via SteamClient raises SteamAuthenticatorError("Only available via MobileWebAuth").

After removal, Steam Guard falls back to email codes.

.get_confirmation_key(tag='', timestamp=None)

Generate an HMAC-SHA1 signature over (timestamp, tag) using the authenticator's identity_secret. Used to sign trade confirmations. Valid tags:

  • 'conf' — load the confirmations page.
  • 'details' — load details about a trade.
  • 'allow' — confirm a trade.
  • 'cancel' — cancel a trade.

.create_emergency_codes(code=None)

Two-step because Steam requires a fresh SMS code:

sa.create_emergency_codes()              # request SMS
sa.create_emergency_codes(code='12345')  # returns list of emergency codes

.destroy_emergency_codes()

Invalidate all emergency codes for the account.

.status()

Fetch the current authenticator status from Steam. Returns a dict with state, token_gid, etc.

Phone number management

Steam requires a verified phone number to add an authenticator. If the account doesn't have one:

sa.add_phone_number('+15551234567')     # 1. request add
sa.confirm_email()                      # 2. only if resp said email_confirmation=True
sa.confirm_phone_number('SMS_CODE')     # 3. finalize with SMS

Utility checks:

  • sa.has_phone_number() — is one already on file?
  • sa.validate_phone_number(number) — is this number valid for Steam? (Rejects VoIP.)

Time alignment

TOTP codes are time-sensitive. Steam's servers run in their own time frame; if your clock is off, codes fail. Fixing that:

sa.align_time_every = 60           # re-check Steam time every 60s
sa.steam_time_offset               # cached offset, in seconds

sa.get_time() handles the alignment. Or manually via steam.guard.get_time_offset().

Bulk-extract secrets from Android

For a rooted Android phone with the Steam Guard app installed:

from steam.guard import extract_secrets_from_android_rooted

secrets_by_steamid = extract_secrets_from_android_rooted()
# {int(steamid): {...secrets dict...}, ...}

Requires adb binary + rooted device + debug mode. See docstring in steam/guard.py for details.

Secrets format

sa.secrets is a dict with:

  • account_name — Steam username.
  • identity_secret — base64. Used for get_confirmation_key.
  • revocation_code — R-prefixed string. Save this — needed for remove().
  • secret_1 — base64. Purpose unknown to us; Steam wants it.
  • serial_number — decimal string, serial of the authenticator.
  • server_time — Unix timestamp of when the authenticator was created.
  • shared_secret — base64. Used for get_code().
  • status — int. 1 = active.
  • token_gid — hex GID for the token.
  • uriotpauth://totp/... URL usable by generic TOTP apps (though the alphabet is Steam-custom).

Format is Steam-Desktop-Authenticator (SDA) compatible — you can hand mysecrets.json to SDA or WinAuth and they'll accept it.

Where to go next

  • The mobile login flow: WebAuthMobileWebAuth is the required backend for full functionality.
  • Actually signing a trade confirmation? Use get_confirmation_key() to build the k=<hex> parameter, then POST to the trade-confirmation endpoint yourself. steam.guard doesn't wrap the full trade-confirmation flow.

Clone this wiki locally