Concierge.Auth.Client.AuthGuard 3.1.1

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

Concierge.Auth.Client.AuthGuard

Three ASP.NET Core authentication schemes: ConciergeSession validates opaque session tokens (contract §11.7); ConciergeJwt validates machine client-credential JWTs (contract §11.3, RS256/JWKS via Concierge.Auth.Client.Keys's IJwksKeyProvider, then mandatory Redis checks); and a named API-key scheme validates a presented key against a host-supplied loader. WebSocket handshake support covers both the session lane and the JWT lane (contract §7 / §11.9). The only Concierge.Auth.Client.* package that references ASP.NET Core (FrameworkReference, never a Microsoft.AspNetCore.* PackageReference) or StackExchange.Redis.

Session lane (AddConciergeSessionToken) — contract §11.6–§11.7

  1. Extract the opaque token — Authorization: Bearer or the configured httpOnly cookie, per Transport.
  2. GET sess:token:<sha256(token)> on redis-cache — one round trip on cache hit; no HTTP.
  3. Hit → check idleExpiresAt / absoluteExpiresAt; if cookie transport + unsafe method, compare sha256(X-CSRF-Token) to the payload's csrfHash (bound to sessionId).
  4. MissPOST /client-services/v1/:clientKey/sessions/validate with this integration's §10 credential; on success repopulate the cache (TTL = min(idleRemaining, absoluteRemaining, 900) s, floored at 1 — matches AuthService SessionService.CacheTtlCapSeconds, not configurable).
  5. Miss + unknown/revoked/expired → write negative cache ("0", NegativeCacheSeconds, default 30 s) and reject.
  6. Redis unreachable503 + Retry-After: 5. Never authenticate.
  7. On success attach { userId, sessionId } (ConciergeRequestContext) — never roles, scopes, permissions, tenant, email, or displayName.

Fails closed at registration, synchronously: RedisConnectionString, AuthServiceBaseUrl, ClientKey, and ClientSecret are all required (OptionsValidationException per property). Transport=Cookie with EnableCsrfProtection=false is refused — same rule as contract §11.5.

services
    .AddConciergeAuthGuard()
    .AddConciergeSessionToken(configuration)   // scheme "ConciergeSession" — humans
    .AddConciergeJwtBearer(configuration);     // scheme "ConciergeJwt"     — machines
// appsettings.json — SessionValidation section
{
  "Concierge": {
    "AuthClient": {
      "SessionValidation": {
        "RedisConnectionString": "localhost:6379",
        "AuthServiceBaseUrl": "https://auth.example",
        "ClientKey": "my-service",
        "ClientSecret": "<from credential store>",
        "Transport": "Bearer",
        "CookieName": "concierge_session_at",
        "EnableCsrfProtection": false,
        "NegativeCacheSeconds": 30
      }
    }
  }
}

Target a scheme explicitly when registering more than one: [Authorize(AuthenticationSchemes = ConciergeSessionDefaults.AuthenticationScheme)].

JWT lane (AddConciergeJwtBearer) — contract §5 / §11.3

  1. Signature/JWKS. RS256 only, via IJwksKeyProvider. ClockSkew is hardcoded to 5 seconds. JsonWebTokenHandler, not the legacy JwtSecurityTokenHandler.
  2. typ guard (§11.3). The ConciergeJwt scheme requires typ: "client"; any token with no typ is rejected. The WebSocket / user-context path (ValidateAsync) rejects any payload typ claim, including typ: "client".
  3. client-revoked-at:<sub> epoch (T-0132). Client-credential tokens whose iat predates the Redis epoch are rejected; Redis unreachable → fail closed (same posture as the deny-list).
  4. jti deny-list — MANDATORY after the above succeed. EXISTS denylist:jti:<jti>.

On JWT success, { sub, jti } is attached as { userId, jti }. On session success, { userId, sessionId }.

Install and register (JWT + API key)

services.AddConciergeAuthClient(configuration, db => db.UseNpgsql(cs, npgsql =>
    npgsql.MigrationsHistoryTable("__EFMigrationsHistory", "concierge"))); // base — required first
services.AddConciergeAuthClientKeys(configuration);    // Keys — supplies IJwksKeyProvider

services
    .AddConciergeAuthGuard()
    .AddConciergeJwtBearer(configuration);       // binds "Concierge:AuthClient:TokenValidation"

Fails closed at this call, synchronously (decision D8): if ConciergeTokenValidationOptions.RedisConnectionString is not configured, AddConciergeJwtBearer throws OptionsValidationException immediately.

// appsettings.json — TokenValidation section
{
  "Concierge": {
    "AuthClient": {
      "TokenValidation": {
        "RedisConnectionString": "localhost:6379",
        "RedisDatabase": 0
      }
    }
  }
}

Standard ASP.NET Core pipeline — no custom UseX call:

app.UseAuthentication();
app.UseAuthorization();

[Authorize] (no scheme specified) targets whichever scheme was registered first. Read attached context downstream:

ConciergeRequestContext? ctx = httpContext.GetConciergeRequestContext();

API key scheme

AddConciergeApiKey(schemeName, configure) registers one independent scheme per call — one per partner/integration, for example. It ships no default credential source: configure MUST set SecretLoader, and a scheme registered without one fails at options resolution (OptionsValidationException) — fail-closed, same contract as the JWT scheme's Redis check.

services.AddSingleton<IConciergeCredentialCache, MyRedisCredentialCache>(); // host-supplied — see below

services
    .AddConciergeAuthGuard()
    .AddConciergeApiKey("partnerA", options =>
    {
        options.HeaderName = "X-Api-Key"; // default
        options.SecretLoader = (sp, ct) =>
            sp.GetRequiredService<IClientCredentialStore>().GetActiveSecretAsync(ct);
    });

The resolved secret is cached under the scheme name ("partnerA") in IConciergeCredentialCache so SecretLoader is not called on every request; CacheTtl (default 5 minutes) is advisory. Comparison against the presented header value is constant-time.

IConciergeCredentialCache — host-supplied, no default shipped

Defined in the base package (Concierge.Auth.Client.Caching). This package ships no implementation — in-memory, Redis, a database, whatever fits — the same provider-agnosticism the base package already applies to EF Core:

public interface IConciergeCredentialCache
{
    Task<string?> GetAsync(string key, CancellationToken cancellationToken);
    Task SetAsync(string key, string value, TimeSpan? ttl, CancellationToken cancellationToken);
}

Multiple schemes

AddConciergeSessionToken, AddConciergeJwtBearer, and AddConciergeApiKey are independent — register any combination. Whichever call runs first becomes AuthenticationOptions.DefaultScheme, plain registration-order, no priority flag:

services
    .AddConciergeAuthGuard()
    .AddConciergeSessionToken(configuration)          // default when first
    .AddConciergeJwtBearer(configuration)
    .AddConciergeApiKey("partnerA", o => o.SecretLoader = ...);

Target a specific scheme with [Authorize(AuthenticationSchemes = "partnerA")]; custom scheme names are available on session and JWT registration overloads too.

WebSocket handshake (contract §7 / §11.9)

The token arrives explicitly in the handshake payload (auth: { token }), not a header — extract it with whatever WebSocket library the host uses, then:

// Human lane — opaque session token OR user-context JWT (no typ claim):
var result = await webSocketAuthenticator.AuthenticateHandshakeAsync(token, cancellationToken);

// Machine lane — client-credential JWT (typ: "client"):
var clientResult = await webSocketAuthenticator.AuthenticateClientHandshakeAsync(token, cancellationToken);

if (result.IsFailure)
{
    // reject the handshake — same failure semantics as the HTTP path
}

// after accepting the socket:
await webSocketAuthenticator.RunUntilExpiryAsync(socket, result.Value.ExpiresAt, cancellationToken);

AuthenticateHandshakeAsync routes opaque tokens through ISessionValidator (same cache-hit → validate-fallback → negative-cache sequence as HTTP) and JWT-shaped tokens through IConciergeTokenValidator in user-context mode. AuthenticateClientHandshakeAsync uses ValidateClientTokenAsync, including the typ guard and client-revocation epoch.

RunUntilExpiryAsync closes the socket once the credential's expiry passes — a long-lived connection must not outlive the credential that opened it. For sessions, ExpiresAt is the earlier of idleExpiresAt and absoluteExpiresAt from the live payload.

Failure codes

Session lane

  • SESSION_INVALID — unknown, revoked, expired, reused, or negative-cached session.
  • CSRF_TOKEN_INVALID — cookie transport, unsafe method, missing/wrong CSRF (403).
  • SESSION_STORE_UNAVAILABLE — Redis unreachable (503 + Retry-After: 5).

JWT lane

  • TOKEN_INVALID — malformed, wrong key, wrong/disallowed algorithm, expired, wrong/missing typ.
  • TOKEN_DENIED — signature valid but jti deny-listed or iat before client-revocation epoch.
  • DENYLIST_UNAVAILABLE — deny-list or client-revocation store unreachable; request rejected.

Out of scope

Any authorization decision, any PolicyService call, profile operations, credential rotation (see Concierge.Auth.Client.Secrets), JWKS fetching (see Concierge.Auth.Client.Keys), obtaining session tokens (see Concierge.Auth.Client.Sessions).

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

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
3.1.1 141 9/10/2026
3.1.0 95 8/26/2026
3.0.0 393 8/24/2026
2.0.0 239 8/17/2026
1.0.0 109 8/17/2026