Skip to main content

⚡ fastapi-steam

PyPI version PyPI downloads Python 3.10+ License MIT Status Ruff

fastapi-steam - async Steam OpenID 2.0 authentication for FastAPI / Starlette

(─‿‿─)

 ,---.                ,--.                 ,--. 
/  .-' ,--,--. ,---.,-'  '-. ,--,--. ,---. `--' 
|  `-,' ,-.  |(  .-''-.  .-'' ,-.  || .-. |,--. 
|  .-'\ '-'  |.-'  `) |  |  \ '-'  || '-' '|  | 
`--'   `--`--'`----'  `--'   `--`--'|  |-' `--' 
                                    `--'        
                                        
        ,--.                            
 ,---.,-'  '-. ,---.  ,--,--.,--,--,--. 
(  .-''-.  .-'| .-. :' ,-.  ||        | 
.-'  `) |  |  \   --.\ '-'  ||  |  |  | 
`----'  `--'   `----' `--`--'`--`--`--' 

📦 installation

pip install fastapi-steam
pip install fastapi-steam[fastapi]  # with the FastAPI integration (SteamAuth)
pip install "fastapi-steam[fastapi]" uvicorn  # to run examples/

Full working examples are in the examples/ directory - a complete app with sessions and a SQLite user table.


📑 quick start

from fastapi import FastAPI, Request
from fastapi_steam import SteamAuth, SteamUser

steam = SteamAuth(
    api_key="...",  # Steam Web API key
    redirect_uri="http://localhost:8000/auth/callback",
)

app = FastAPI()


@app.get("/login")
async def login(request: Request):
    token = steam.generate_state()
    request.session["steam_state"] = token
    return steam.redirect(state=token)


@app.get("/auth/callback")
async def callback(request: Request) -> SteamUser:
    user: SteamUser = await steam.authenticate(
        request,
        state=request.session["steam_state"],
    )
    return user

🧩 features

  • 🚀 async-native - single reusable httpx.AsyncClient, no thread-pool blocking
  • 🎯 OpenID 2.0 done right - 9 spec checks before the callback is trusted
  • 🛡️ CSRF state token - OpenID 2.0 has no state; bind an unguessable token via return_to
  • 🧩 framework-agnostic core - SteamOpenIDClient has zero web-framework dependencies
  • FastAPI integration - SteamAuth shipped as an optional [fastapi] extra (lazy import, no hard dependency)
  • 📦 player summaries - ISteamUser/GetPlayerSummaries/v0002 mapped with Pydantic v2
  • fully typed - py.typed marker, mypy --strict clean
  • 🐍 Python 3.10+

⚖️ comparison with alternatives

Feature fastapi-steam social-auth-core authlib manual (DIY)
Steam OpenID 2.0 ❌ (OAuth 2.0 / OIDC only) 🔧
Async (asyncio / httpx) ❌ (requests) 🔧
Spec response validation (9 checks) partial 🔧
CSRF state token 🔧
Framework-agnostic core ⚠️ needs strategy/storage glue -
FastAPI-native integration ⚠️ manual wiring 🔧
Steam player summary fetch ✅ built-in 🔧
Pydantic v2 models -
py.typed, mypy --strict clean -
Runtime deps (basic mode) httpx + pydantic requests + many httpx -
Python versions 3.10+ 3.10+ 3.8+ -

Each library has its own strengths - choose what fits your use case.


📖 usage

SteamAuth (FastAPI integration)

state is mandatory for every SteamAuth call - generate a token with generate_state(), pass it to redirect() and hand it to authenticate():

from fastapi import FastAPI, Request
from fastapi_steam import SteamAuth, SteamUser

steam = SteamAuth(
    api_key="...",
    redirect_uri="http://localhost:8000/auth/callback",
)


@app.get("/login")
async def login(request: Request):
    token = steam.generate_state()
    request.session["steam_state"] = token
    return steam.redirect(state=token)  # 307 to the Steam login page


@app.get("/auth/callback")
async def callback(request: Request) -> SteamUser:
    return await steam.authenticate(
        request,
        state=request.session["steam_state"],
    )

Lifecycle: SteamAuth owns an httpx.AsyncClient. Use it as an async context manager or close it explicitly:

async with steam:  # closes the internal client on exit
    user = await steam.authenticate(request, state=request.session["steam_state"])
# or, for a long-lived instance:
await steam.aclose()

Why is state required?

OpenID 2.0 has no state field (unlike OAuth 2.0) and Steam does not issue per-login nonces you can verify locally. Without an unguessable token bound to the session, an attacker can launch a login CSRF attack: they start a Steam login in your app and trick the victim into finishing it, logging the victim into the attacker's account.

The equivalent protection is a random token embedded in return_to, which binds the callback to the session that started the login. SteamAuth refuses to run without it - authenticate() and redirect() raise ValueError if the token is missing, and authenticate() also requires redirect_uri so the callback can be verified against the exact URL used at login time.

Framework-agnostic usage

SteamOpenIDClient is independent of any web framework:

import httpx
from fastapi_steam import SteamOpenIDClient

async def main() -> None:
    client = SteamOpenIDClient(api_key="...")

    login_url = client.get_login_url("https://example.com/auth/callback")
    # redirect the user's browser to `login_url`...

    # after Steam redirects back, pass the raw query parameters:
    steam_id = await client.verify_response(
        {"openid.claimed_id": "...", "openid.mode": "id_res", ...}
    )

    user = await client.get_user_summary(steam_id)
    print(user.personaname, user.profile_url)

The core does not import fastapi; importing it never pulls in a web framework.

Custom httpx client

A custom httpx.AsyncClient can be injected; the library will never close it:

transport = httpx.AsyncHTTPTransport(retries=3, pool_connections=20)
async with httpx.AsyncClient(transport=transport) as http:
    steam = SteamOpenIDClient(api_key="...", client=http)

🛡️ security

verify_response performs the following checks on every callback, mirroring OpenID Authentication 2.0:

  1. openid.ns must be http://specs.openid.net/auth/2.0.
  2. openid.mode must be id_res (a cancel response raises OpenIDError).
  3. openid.sig and openid.signed must be present.
  4. openid.claimed_id must match ^https://steamcommunity\.com/openid/id/(\d+)$.
  5. openid.identity must equal openid.claimed_id.
  6. openid.op_endpoint must point to Steam.
  7. openid.return_to must match the return_to used when the login was initiated.
  8. The CSRF state token (constant-time compared) must match the one issued at login.
  9. The parameters are re-posted to https://steamcommunity.com/openid/login with openid.mode=check_authentication and the response must contain is_valid:true.

⚠️ steam's own limits

Steam does not implement OpenID 2.0 fully, so two spec checks are impossible against it:

  • local verification of openid.sig - Steam always issues a dummy assoc_handle;
  • re-verification of the signed fields echoed by check_authentication - Steam only returns ns and is_valid.

is_valid:true from the provider is the authoritative check.


📝 models

SteamUser mirrors ISteamUser/GetPlayerSummaries/v0002 (API key aliases handled transparently - steamid, profileurl, avatarfull, ...).

Field Type Description
steam_id str SteamID64 (always a string, never an int)
personaname str profile display name
profile_url HttpUrl Steam community profile URL
avatar HttpUrl small avatar (32px)
avatar_full HttpUrl full-size avatar
community_visibility_state int profile visibility (1 = private, 2 = friends only, 3 = public)
profilestate int | None profile configured (1 = yes)
lastlogoff int | None unix timestamp of last logoff
realname str | None real name
timecreated int | None account creation timestamp
loccountrycode str | None two-letter country code

🔗 API Reference

SteamAuth(api_key, *, redirect_uri, client, timeout)

FastAPI integration. Requires the [fastapi] extra.

Parameter Type Default Description
api_key str | None None Steam Web API key for GetPlayerSummaries
redirect_uri str | None None absolute URL of the /auth/callback route (required by login_url/authenticate)
client httpx.AsyncClient | None None inject your own client (never closed)
timeout httpx.Timeout | float 10.0 request timeout

Methods (all CSRF methods require state):

  • generate_state()str - fresh CSRF token
  • login_url(state)str - Steam login URL (raises ValueError without redirect_uri)
  • redirect(state)RedirectResponse - 307 to Steam
  • await authenticate(request, *, state)SteamUser - validate callback + fetch summary (raises ValueError without redirect_uri or state)
  • await aclose() / async with steam: - lifecycle

SteamOpenIDClient(api_key, *, client, timeout)

Framework-agnostic core. No web-framework dependency.

Parameter Type Default Description
api_key str | None None Steam Web API key for GetPlayerSummaries
client httpx.AsyncClient | None None inject your own client (never closed)
timeout httpx.Timeout | float 10.0 request timeout

Methods:

  • get_login_url(return_to, *, realm=None, state=None)str
  • with_state(return_to, state)str (static)
  • generate_state()str (static)
  • await verify_response(params, *, return_to=None, state=None)str (SteamID64)
  • await get_user_summary(steam_id)SteamUser
  • await aclose()

Exceptions

Exception Description
SteamAuthError base class for all library errors
InvalidSignatureError malformed/forged response or is_valid:false
OpenIDError provider reported openid_error or the user cancelled the login (mode=cancel)
SteamAPIError Steam Web API / transport failures or malformed payloads (carries .status_code)
UserNotFoundError no player profile for the given steam_id

📜 license

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_steam-0.1.0.tar.gz (19.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_steam-0.1.0-py3-none-any.whl (14.2 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_steam-0.1.0.tar.gz.

File metadata

  • Download URL: fastapi_steam-0.1.0.tar.gz
  • Upload date:
  • Size: 19.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for fastapi_steam-0.1.0.tar.gz
Algorithm Hash digest
SHA256 83ed5450b88e4acbd4b9743a0267a7e3e26dc565c311eac81b60a31958d8e117
MD5 7a81b1ffc782e468b459277f53e30e29
BLAKE2b-256 ade40c3e9e47a75caa203e34bed3024885c194668a7b253181c0c13d5a6e760f

See more details on using hashes here.

File details

Details for the file fastapi_steam-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fastapi_steam-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for fastapi_steam-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5a36877463ffede797ab81ee4123c43e2f231d764cf28fcf58b9b7b8d02c0c0a
MD5 1b5b33e7925ef9853c26d88237ad228a
BLAKE2b-256 7689c7a41ecd22c2ab2d706ae131151d67564492a2bbc6f79b7940ae8571229d

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page