Noodloft.Identity.Client
0.0.1-alpha1
dotnet add package Noodloft.Identity.Client --version 0.0.1-alpha1
NuGet\Install-Package Noodloft.Identity.Client -Version 0.0.1-alpha1
<PackageReference Include="Noodloft.Identity.Client" Version="0.0.1-alpha1" />
<PackageVersion Include="Noodloft.Identity.Client" Version="0.0.1-alpha1" />
<PackageReference Include="Noodloft.Identity.Client" />
paket add Noodloft.Identity.Client --version 0.0.1-alpha1
#r "nuget: Noodloft.Identity.Client, 0.0.1-alpha1"
#:package Noodloft.Identity.Client@0.0.1-alpha1
#addin nuget:?package=Noodloft.Identity.Client&version=0.0.1-alpha1&prerelease
#tool nuget:?package=Noodloft.Identity.Client&version=0.0.1-alpha1&prerelease
Noodloft.Identity.Client
Authenticate against a Noodloft Identity server from .NET, and call your own APIs with the resulting token.
Targets net8.0 and net10.0. Its only dependency is Microsoft.Extensions.Http.
Install
dotnet add package Noodloft.Identity.Client
Native login (no browser)
The native flow is a loop: send what you have, and the server tells you what is still missing. This is how you sign a user in from a desktop app, a game, or a CLI without opening a browser. It requires a first-party client.
var client = new NoodloftAuthClient(new NoodloftClientOptions
{
BaseUrl = "https://id.example.com",
TenantSlug = "acme",
ClientId = "acme-native",
RedirectUri = "com.example.app://callback", // registered exactly on this client
Scope = "openid profile email offline_access" // only granted scopes
});
// Users sign in with their USERNAME, not their email address.
var step = await client.StartNativeLoginAsync(username, password);
while (step.IsPending)
{
if (step.NeedsEnrollment)
{
// The tenant requires MFA and this user has nothing enrolled yet — enroll inline.
step = await client.BeginTotpEnrollmentAsync(step);
ShowQrCode(step.TotpUri); // or step.TotpSecretBase32 for manual entry
step = await client.ConfirmTotpEnrollmentAsync(step, await PromptAsync("code"));
ShowRecoveryCodesOnce(step.BackupCodes); // shown once, never retrievable again
}
else
{
// step.RequiredFactor is one of: otp, email_otp, sms_otp, backup_code, webauthn
step = await client.SubmitFactorAsync(step, await PromptAsync(step.RequiredFactor));
}
}
// Signed in. Tokens are already saved to the token store.
Browser login (PKCE)
Third-party clients must use the browser flow. Open the URL, capture the redirect, verify the state, exchange.
var request = client.BeginWebLogin();
OpenInBrowser(request.Url);
var (code, state) = await WaitForRedirectAsync();
if (state != request.State) throw new InvalidOperationException("State mismatch — discard this response.");
await client.CompleteWebLoginAsync(request, code);
For browser step-up, call BeginWebLogin(acrValues: "mfa", maxAgeSeconds: 300).
The current client package does not generate DPoP proofs or configure an mTLS client certificate. If discovery
returns require_sender_constrained_tokens: true, use a DPoP-capable OAuth library or a trusted mTLS gateway for
token acquisition and API calls.
Calling your API
var api = new NoodloftApiClient(client, new Uri("https://api.example.com"));
var orders = await api.GetAsync<List<Order>>("/orders");
NoodloftApiClient attaches the bearer token, and on a 401 refreshes once and retries. Failures are typed so you
can tell them apart:
| Exception | Meaning |
|---|---|
NoodloftAuthException with Error == "not_signed_in" |
The token was rejected and refreshing did not help. Sign in again. |
NoodloftAuthException with Error == "insufficient_user_authentication" |
Step-up required. Re-authenticate with acrValues: "mfa". |
NoodloftAuthException with Error == "insufficient_permission" |
Authenticated, but lacking the permission. Retrying will not help. |
NoodloftApiException |
Your API returned a non-auth error. Body carries the response. |
Dependency injection
builder.Services.AddNoodloftClient(opts =>
{
opts.BaseUrl = "https://id.example.com";
opts.TenantSlug = "acme";
opts.ClientId = "acme-spa";
opts.RedirectUri = "https://app.example.com/callback";
opts.Scope = "openid profile email offline_access";
opts.ApiBaseUrl = "https://api.example.com"; // enables relative paths on injected NoodloftApiClient
});
Both clients are registered scoped over a pooled named HttpClient. A token store holds one user's session, so
a singleton would leak it across requests.
Storing tokens
The bundled InMemoryTokenStore loses the session when the process exits. To keep users signed in, implement
ITokenStore over something durable and encrypted — DPAPI or the platform keyring on a desktop, the secure enclave
on mobile, an encrypted IDistributedCache entry in a web backend.
The refresh token can mint new access tokens until revoked. Treat it like a password.
Notes
- Access tokens are opaque. Do not try to decode one — APIs introspect them against the server. A resource-side
cache can delay revocation until its TTL unless push eviction is configured. Use
GetUserInfoAsync()for identity details. - Every endpoint is tenant-scoped under
/t/{TenantSlug}/. The SDK derives those URLs for you. - Refresh tokens rotate and are single-use. Concurrent calls on one
NoodloftAuthClientinstance are serialized. If several client instances or processes share one token store, that store must provide cross-instance coordination as well.
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
- Microsoft.Extensions.Http (>= 10.0.9)
-
net8.0
- Microsoft.Extensions.Http (>= 10.0.9)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Noodloft.Identity.Client:
| Package | Downloads |
|---|---|
|
Noodloft.Components.Games.Godot
Godot 4 glue for Noodloft components: logging, user:// save and encrypted token storage, and the WebSocket relay MultiplayerPeer used by server-authoritative games. The nodes and resources are NOT in this package. Godot keys C# script types to a res:// path, which a type inside a DLL does not have, so they ship as addon source instead. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.0.1-alpha1 | 230 | 8/1/2026 |