⚡ fastapi-steam
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 viareturn_to - 🧩 framework-agnostic core -
SteamOpenIDClienthas zero web-framework dependencies - ⚡ FastAPI integration -
SteamAuthshipped as an optional[fastapi]extra (lazy import, no hard dependency) - 📦 player summaries -
ISteamUser/GetPlayerSummaries/v0002mapped with Pydantic v2 - ✅ fully typed -
py.typedmarker,mypy --strictclean - 🐍 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:
openid.nsmust behttp://specs.openid.net/auth/2.0.openid.modemust beid_res(acancelresponse raisesOpenIDError).openid.sigandopenid.signedmust be present.openid.claimed_idmust match^https://steamcommunity\.com/openid/id/(\d+)$.openid.identitymust equalopenid.claimed_id.openid.op_endpointmust point to Steam.openid.return_tomust match thereturn_toused when the login was initiated.- The CSRF
statetoken (constant-time compared) must match the one issued at login. - The parameters are re-posted to
https://steamcommunity.com/openid/loginwithopenid.mode=check_authenticationand the response must containis_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 dummyassoc_handle; - re-verification of the signed fields echoed by
check_authentication- Steam only returnsnsandis_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 tokenlogin_url(state)→str- Steam login URL (raisesValueErrorwithoutredirect_uri)redirect(state)→RedirectResponse- 307 to Steamawait authenticate(request, *, state)→SteamUser- validate callback + fetch summary (raisesValueErrorwithoutredirect_uriorstate)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)→strwith_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)→SteamUserawait 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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83ed5450b88e4acbd4b9743a0267a7e3e26dc565c311eac81b60a31958d8e117
|
|
| MD5 |
7a81b1ffc782e468b459277f53e30e29
|
|
| BLAKE2b-256 |
ade40c3e9e47a75caa203e34bed3024885c194668a7b253181c0c13d5a6e760f
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a36877463ffede797ab81ee4123c43e2f231d764cf28fcf58b9b7b8d02c0c0a
|
|
| MD5 |
1b5b33e7925ef9853c26d88237ad228a
|
|
| BLAKE2b-256 |
7689c7a41ecd22c2ab2d706ae131151d67564492a2bbc6f79b7940ae8571229d
|