Sudomimus.Native
4.0.0
dotnet add package Sudomimus.Native --version 4.0.0
NuGet\Install-Package Sudomimus.Native -Version 4.0.0
<PackageReference Include="Sudomimus.Native" Version="4.0.0" />
<PackageVersion Include="Sudomimus.Native" Version="4.0.0" />
<PackageReference Include="Sudomimus.Native" />
paket add Sudomimus.Native --version 4.0.0
#r "nuget: Sudomimus.Native, 4.0.0"
#:package Sudomimus.Native@4.0.0
#addin nuget:?package=Sudomimus.Native&version=4.0.0
#tool nuget:?package=Sudomimus.Native&version=4.0.0
Sudomimus.Native
C# SDK for the Sudomimus Native API. Exchanges a Steam Web API auth ticket for application access and refresh tokens in a single round trip.
Mirrors the @sudomimus/native
TypeScript SDK. Generic .NET 8 package — no Steam SDK, no Godot dependency.
Works in plain console apps, Godot, Unity, ASP.NET Core, or any .NET host.
using Sudomimus.Native;
using var http = new HttpClient();
var client = new NativeClient(NativeClient.ProductionBaseUrl, http);
var response = await client.DirectIssueSteamTicketAsync(new DirectIssueSteamTicketRequest
{
ApplicationAnchor = "anchor-xxx",
SteamTicketHex = ticketHex, // bytes from ISteamUser::GetAuthTicketForWebApi("sudomimus"), hex-encoded
SteamAppId = 480,
});
// response.AccessToken / response.RefreshToken — parse with Sudomimus.Token.
For tickets to verify, the calling Steam client SDK must pass identity
"sudomimus" to GetAuthTicketForWebApi(identity) — Steam binds the issued
ticket to the identity string and the server hardcodes the same value. See
NativeConstants.SteamTicketIdentity.
Error handling
Non-success responses throw NativeApiException; inspect StatusCode and
Reason. A 409 distinguishes NativeReason.ReplayProtectionAlreadySeen
from NativeReason.AuthorizationArtifactStale. Steam and access-key admission
budgets or failure circuits return 429; unavailable admission counters
return 503. Both use empty bodies, so Reason is null and the caller
should back off before retrying.
Claims on every response
Every direct-issue 200 carries a claims view — the per-claim policy joined
with the user's standing decision. It answers "why is email absent from my
token?": OFF (the app never asks), UNKNOWN (user never decided), DENIED
(declined), or granted.
ClaimsStateView claims = response.Claims;
// claims.Email.Requirement -> ClaimRequirement.Off / Optional / Required / SyntheticOnly / SyntheticFallback
// claims.Email.State -> ClaimGrantState.Unknown / Granted / Denied
bool realEmailShared = claims.Email.Requirement != ClaimRequirement.Off
&& claims.Email.State == ClaimGrantState.Granted;
Errand recovery for claim-gated logins
When an application marks a claim REQUIRED and the user has not yet granted
it (or the account lacks the data, e.g. a Steam-first account with no email),
direct-issue cannot prompt — it returns a 403 whose body carries an errand
handoff: a short-lived browser URL where the user grants consent / completes
the missing data. After they finish, retrying direct-issue succeeds.
You can drive this by hand off NativeApiException …
catch (NativeApiException ex) when (ex.IsClaimGate)
{
// ex.Errand.Url — open in the system browser
// ex.Errand.ErrandKey — poll GetErrandStatusAsync(key) until COMPLETED
// ex.Claims — what is still owed
}
… or let NativeAuthenticator run the loop for you. It does not open the
browser itself (the SDK can't know your host) — it calls the OpenUrl callback
you supply (Godot OS.ShellOpen, Unity Application.OpenURL, console
Process.Start). Two invocation styles:
Automatic — opens the browser, polls the errand, retries, returns tokens:
var auth = new NativeAuthenticator(client, new NativeAuthenticatorOptions
{
OpenUrl = (uri, _) => { /* OS.ShellOpen / Process.Start */ return Task.CompletedTask; },
Progress = new Progress<ErrandProgress>(p => Console.WriteLine($"[errand] {p.Phase}")),
});
DirectIssueResult login = await auth.AuthenticateAccessKeyAsync(new DirectIssueAccessKeyRequest
{
ApplicationAnchor = "anchor-xxx",
AccessKeyIdentifier = id,
AccessKeySecret = secret,
});
// login.AccessToken / login.RefreshToken / login.Claims
For Steam, pass a factory so each attempt (and each retry) gets a fresh, single-use ticket:
DirectIssueResult login = await auth.AuthenticateSteamTicketAsync(async ct =>
new DirectIssueSteamTicketRequest
{
ApplicationAnchor = "anchor-xxx",
SteamTicketHex = await AcquireFreshTicketHexAsync(ct),
SteamAppId = 480,
});
Manual — opens the browser and hands the errand back; your app decides when to retry (e.g. an "I'm done" button). No polling:
DirectIssueOutcome outcome = await auth.TryAuthenticateAccessKeyAsync(request);
if (outcome is DirectIssueOutcome.ErrandRequired errand)
{
// browser already opened; show your own "finished?" affordance, then:
outcome = await auth.TryAuthenticateAccessKeyAsync(request); // retry on the user's signal
}
if (outcome is DirectIssueOutcome.Authenticated ok)
{
// ok.Result.AccessToken / ok.Result.RefreshToken / ok.Result.Claims
}
Non-claim-gate failures (Layer denials, account disabled, replay, …) propagate
as NativeApiException in both styles — only the recoverable claim gate is
handled.
sequenceDiagram
participant App as NativeAuthenticator
participant Native as native-api
participant Browser as System browser → core-ui /errand
App->>Native: POST /direct-issue/* (attempt)
Native-->>App: 403 { reason, claims, errand{ errandKey, url, expiresAt } }
App->>Browser: OpenUrl(errand.url)
Browser->>Browser: (re-auth) → register email → complete name → consent
loop automatic mode, every ~2s
App->>Native: GET /errand/{errandKey}/status
Native-->>App: { status: PENDING }
end
Native-->>App: { status: COMPLETED }
App->>Native: POST /direct-issue/* (retry, fresh Steam ticket)
Native-->>App: 200 { accessToken, refreshToken, claims }
| 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 was computed. 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. |
-
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.