Dloizides.Steam
0.2.0
dotnet add package Dloizides.Steam --version 0.2.0
NuGet\Install-Package Dloizides.Steam -Version 0.2.0
<PackageReference Include="Dloizides.Steam" Version="0.2.0" />
<PackageVersion Include="Dloizides.Steam" Version="0.2.0" />
<PackageReference Include="Dloizides.Steam" />
paket add Dloizides.Steam --version 0.2.0
#r "nuget: Dloizides.Steam, 0.2.0"
#:package Dloizides.Steam@0.2.0
#addin nuget:?package=Dloizides.Steam&version=0.2.0
#tool nuget:?package=Dloizides.Steam&version=0.2.0
Dloizides.Steam
Steam OpenID 2.0 verification, Steam Web API reads, and a daily call-quota guard for ASP.NET Core services.
Install
dotnet add package Dloizides.Steam
builder.Services.AddDloizidesSteam(o =>
{
o.ApiKey = builder.Configuration["Steam:ApiKey"]!;
o.DailyCallBudget = 90_000;
});
The named HttpClient (Dloizides.Steam) carries no retry policy of its own —
Dloizides.ServiceDefaults already attaches AddStandardResilienceHandler() to it. Adding another
would multiply the call count against a hard daily ceiling.
The security boundary
openid.claimed_id arrives on your return URL as a plain query parameter. It is
attacker-controlled. Anyone can hit /auth/steam/return?openid.claimed_id=<any steam id> and,
if you read that parameter, log in as that person.
SteamOpenIdVerifier.VerifyAsync accepts an identity only when both hold:
- the entire received query, re-posted to
https://steamcommunity.com/openid/loginwithopenid.mode=check_authentication, comes back containingis_valid:true; and - the
claimed_idmatches^https://steamcommunity\.com/openid/id/[0-9]{17}$exactly.
Anything else returns null — a forged signature, a missing is_valid key, a non-success status
from Steam, an unreachable Steam, a claimed_id on another host, or a claimed_id of the wrong
shape even when Steam says the assertion is valid. [0-9] rather than \d is deliberate: .NET's
\d also matches non-ASCII decimal digits.
var steamId64 = await verifier.VerifyAsync(query, ct);
if (steamId64 is null) return Results.Unauthorized();
Absence is not failure
GetOwnedGamesAsync returns an empty list when the profile is private, and throws
SteamApiException on a 429, any non-success status, a transport failure, or a body that is not
JSON. SteamQuotaExhaustedException (a subclass) means the local budget is spent and no request
was sent.
Returning null for both "private" and "rate-limited" is what turned a throttle into silent data
loss elsewhere in this estate. Callers can act on the difference:
try { var games = await client.GetOwnedGamesAsync(steamId64, ct); } // empty == private
catch (SteamApiException ex) when (ex.IsThrottled) { /* retry later, do not persist */ }
Quota
SteamQuotaGuard enforces DailyCallBudget (default 90,000) against Steam's published ceiling of
100,000 calls per key per day, and resets at UTC midnight. Reservations are all-or-nothing, so a
two-call import never half-runs.
The counter lives in process memory: a restart forgets what was spent, and each replica gets its
own budget. Size DailyCallBudget for the replica count, or back the guard with shared state.
API
| Type | Member |
|---|---|
ISteamOpenIdVerifier |
Uri BuildLoginUrl(Uri returnTo, Uri realm) · Task<string?> VerifyAsync(IDictionary<string,string> query, CancellationToken ct) |
ISteamWebApiClient |
Task<IReadOnlyList<SteamOwnedGame>> GetOwnedGamesAsync(string steamId64, CancellationToken ct) · Task<IReadOnlyList<SteamRecentGame>> GetRecentlyPlayedAsync(string steamId64, CancellationToken ct) |
ISteamQuotaGuard |
Task<bool> TryConsumeAsync(int calls, CancellationToken ct) |
| records | SteamOwnedGame(int AppId, string Name, int PlaytimeForeverMinutes) · SteamRecentGame(int AppId, int Playtime2WeeksMinutes) |
Tests
tests/fixtures/owned-games.json is a recorded, anonymised GetOwnedGames response. Mapping tests
run from it, so the suite needs no Steam API key and makes no network call.
Licence
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.