AtomiCloud.Diene.AuthEngine.TestHelper 1.1.0

dotnet add package AtomiCloud.Diene.AuthEngine.TestHelper --version 1.1.0
                    
NuGet\Install-Package AtomiCloud.Diene.AuthEngine.TestHelper -Version 1.1.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="AtomiCloud.Diene.AuthEngine.TestHelper" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AtomiCloud.Diene.AuthEngine.TestHelper" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="AtomiCloud.Diene.AuthEngine.TestHelper" />
                    
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 AtomiCloud.Diene.AuthEngine.TestHelper --version 1.1.0
                    
#r "nuget: AtomiCloud.Diene.AuthEngine.TestHelper, 1.1.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 AtomiCloud.Diene.AuthEngine.TestHelper@1.1.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=AtomiCloud.Diene.AuthEngine.TestHelper&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=AtomiCloud.Diene.AuthEngine.TestHelper&version=1.1.0
                    
Install as a Cake Tool

Diene .NET Auth Engine

Diene's reproducible development environment is managed by Nix. Run direnv allow once, then use pls tasks from the loaded shell.

This branch is the workspace baseline inherited by every downstream sample: split CI/CD, secrets, release configuration, validators, standards, and vendored agent-skill synchronization.

Commands

  • pls setup — synchronize installed diene package skills.
  • pls lint — run every pre-commit gate.
  • pls secret:scan — scan tracked content for secrets.
  • pls skills:sync — rebuild .claude/skills/vendor/ from installed packages.

Standards

Shared standards

Domain-specific documentation belongs under docs/domain/. The docs/standards/contracts/ location is reserved for the separately owned C0 contracts standard.

.NET 10 foundation

CI Unit coverage Integration coverage Commit activity

This branch adds the .NET 10 toolchain, the App/Lib/UnitTest/IntTest sample, merged multi-project coverage, strict and LLM dead-code modes. See the .NET baseline.

Common commands:

  • pls build, pls dev, pls run, and pls preview
  • pls test, pls test:unit, pls test:int, and the coverage variants
  • pls deadcode for the non-blocking review; CI owns strict dn-inspect

The auth contract is documented in docs/domain/auth-engine.md. Production observability is intentionally absent until the observability add-back.

Auth engine packages

NuGet version NuGet downloads Meta coverage

AtomiCloud.Diene.AuthEngine covers both directions of a Logto-compatible auth boundary: server-side token validation with scope and home-landscape policies, and client-side credential acquisition with rotating refresh and session revocation. It also ships the deferred app-handoff mint/redeem module and the full Logto management seam. AtomiCloud.Diene.AuthEngine.TestHelper provides the identity-provider, token, management, deferred-store, and per-backend onboarding fakes consumers must stand in for.

dotnet add package AtomiCloud.Diene.AuthEngine
dotnet add package AtomiCloud.Diene.AuthEngine.TestHelper

Enable the module

The engine is enable-able, not implicit. Register it once and map its endpoints under the configured mount:

using AtomiCloud.Diene.AuthEngine;
using AtomiCloud.Diene.AuthEngine.Config;
using AtomiCloud.Diene.AuthEngine.Module;

var management = LogtoManagementConfig
    .Create("https://logto.example.com", "https://default.logto.app/api", clientId, clientSecret)
    .Get();

var logto = LogtoConfig
    .Create("https://logto.example.com", BuildTimeIssuer, appId, appSecret, management)
    .Get();

var config = AuthEngineConfig
    .Create(logto, HandoffConfig.Default, TokenLifetimeConfig.Default, "home_landscape")
    .Get();

// The library deliberately installs no production store. This implementation
// must be persistent and Consume must claim one nonce atomically.
builder.Services.AddSingleton<IDeferredTokenStore>(persistentDeferredStore);
builder.Services.AddAtomiAuthEngine(config);

var app = builder.Build();
app.MapAtomiAuthEngine(config);

The OIDC issuer is baked in at build time and compared against directly, so a compromised discovery document cannot move trust to another issuer. Only the signing keys come from discovery.

Register AppHandoffExpired with the Problems pipeline through AddAtomiAuthEngineProblems(config) and enable its exception handler. Every malformed, missing, expired, replayed, rebound, deleted, suspended, or upstream-failed redeem then produces the same RFC 9457 410 response without revealing account state.

Use deferred app handoff

One MapAtomiAuthEngine(config) call exposes exactly three routes beneath the configured mount:

Route Contract
POST {mount} Authenticated empty JSON object; returns a 43-character nonce and its 15-minute expiry.
POST {mount}/redeem Strict {nonce, device} JSON; returns the Logto one-time token, current email, and fixed expiresIn: 120.
GET {mount}/session Returns the validated bearer-token session view.

The store receives only the lowercase SHA-256 digest, never the raw nonce. Consume must perform one atomic ActiveClaimed transition, and Settle must make Consumed and Revoked terminal. Redeem re-resolves the stored OIDC subject once, refuses a missing/suspended/email-rebound user, and mints the provider token only after that check. A claimed record is never made active again, including after a process crash or provider failure.

The redeem request is case-sensitive and rejects unknown top-level or device keys. Its device object requires platform (android or ios) and may carry appVersion, osVersion, and model telemetry.

Guard a request

var outcome = await guard.GuardAsync(
    bearerToken,
    "https://api.example.com",
    [new RequireAllScopes("notes:read"), new RequireHomeLandscape(config, "lapras")]);

return outcome.Match(
    claims => Results.Ok(claims.Subject),
    problem => throw problem.ToException());

Refusals are the published Unauthenticated and Unauthorized catalog problems. The distinction is load-bearing: the first means the caller has no established identity and should sign in, the second means an established identity lacks a permission. An absent home-landscape claim is reported separately from a mismatched one, because only the former is resolved by onboarding.

Acquire a service token

var cache = new TokenCache(credentialClient, clock, config.Lifetimes);
var token = await cache.GetAsync("https://api.example.com", ["notes:read"]);

Tokens renew inside the expiry skew rather than after expiry, so a request never carries a token that dies in flight. Lifetimes default to alcohol parity: 10-minute access tokens and 14-day rotating refresh tokens.

Test against the fakes

using var issuer = new TestTokenIssuer("https://logto.example.com/oidc");
var clock = new FakeAuthClock(now);
var validator = new JwtTokenValidator(config, issuer.KeyResolver, clock);

var token = issuer.MintValidFor("user-1", "https://api.example.com", now, TimeSpan.FromMinutes(10), ["notes:read"]);

(await validator.ValidateAsync(token, "https://api.example.com"))
    .ShouldBeAuthorized()
    .ShouldGrantScopes("notes:read");

TestTokenIssuer mints genuinely signed JWTs rather than hand-assembled strings, so a validator with its signature check disabled cannot pass a suite built on it. Advance FakeAuthClock to exercise expiry without waiting.

The TestHelper also provides a genuinely atomic deferred-store fake and a stateful management fake:

var store = new InMemoryDeferredTokenStore(clock);
var management = new FakeAuthManagement();
management.SetUser(new AuthManagementUser("user-1", "owner@example.test", false));

var minter = new DeferredTokenMinter(store, management, clock);
var handoff = (await minter.Mint(
    new DeferredPayload("user-1", "owner@example.test"))).Get();
var exchange = (await minter.Exchange(handoff.Nonce)).Get();

Run nix develop .#ci -c ./scripts/ci/pkg-validate.sh to pack both packages, positive-control their managed public surfaces, prove one mapping call exposes all three routes, validate metadata and symbols, and restore them into a scratch consumer. See the library baseline for release and promotion guidance.

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

NuGet packages (3)

Showing the top 3 NuGet packages that depend on AtomiCloud.Diene.AuthEngine.TestHelper:

Package Downloads
AtomiCloud.Diene.ApiEngine.TestHelper

Fake upstreams, client-tree doubles, response fixtures, and Result-Problem assertions for AtomiCloud.Diene.ApiEngine.

AtomiCloud.Diene.ServerEngine.TestHelper

An in-process controller host, webhook delivery signer, and tri-state reply assertions for AtomiCloud.Diene.ServerEngine.

AtomiCloud.Diene.E2e.TestHelper

Diene family TestHelper bundle plus black-box response assertions.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 197 7/29/2026
1.0.0 306 7/28/2026