⚡ 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
Metadata
Release files for fastapi-steam 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fastapi_steam-0.1.0.tar.gz | 19.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fastapi_steam-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.8 kB
Release files / fastapi_steam-0.1.0.tar.gz
| Download URL | fastapi_steam-0.1.0.tar.gz |
|---|---|
| Size | 19.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
83ed5450b88e4acbd4b9743a0267a7e3e26dc565c311eac81b60a31958d8e117
|
|
BLAKE2b-256 checksum How to use checksums |
ade40c3e9e47a75caa203e34bed3024885c194668a7b253181c0c13d5a6e760f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / fastapi_steam-0.1.0-py3-none-any.whl
| Download URL | fastapi_steam-0.1.0-py3-none-any.whl |
|---|---|
| Size | 14.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5a36877463ffede797ab81ee4123c43e2f231d764cf28fcf58b9b7b8d02c0c0a
|
|
BLAKE2b-256 checksum How to use checksums |
7689c7a41ecd22c2ab2d706ae131151d67564492a2bbc6f79b7940ae8571229d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|