Sudomimus.Native 4.0.0

dotnet add package Sudomimus.Native --version 4.0.0
                    
NuGet\Install-Package Sudomimus.Native -Version 4.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sudomimus.Native" Version="4.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Sudomimus.Native" Version="4.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Sudomimus.Native" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Sudomimus.Native --version 4.0.0
                    
#r "nuget: Sudomimus.Native, 4.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Sudomimus.Native@4.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Sudomimus.Native&version=4.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Sudomimus.Native&version=4.0.0
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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.

Version Downloads Last Updated
4.0.0 106 8/1/2026
3.3.0 111 7/18/2026
3.2.0 111 7/5/2026
3.1.0 119 7/1/2026
3.0.0 121 6/22/2026
1.3.0 118 6/16/2026
1.2.0 120 6/11/2026
1.1.0 126 6/1/2026
1.0.0 116 5/28/2026
0.0.1 110 5/21/2026