Concierge.Auth.Client.AuthGuard 2.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Concierge.Auth.Client.AuthGuard --version 2.0.0
                    
NuGet\Install-Package Concierge.Auth.Client.AuthGuard -Version 2.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="Concierge.Auth.Client.AuthGuard" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Concierge.Auth.Client.AuthGuard" Version="2.0.0" />
                    
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 2.0.0
                    
#r "nuget: Concierge.Auth.Client.AuthGuard, 2.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 Concierge.Auth.Client.AuthGuard@2.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=Concierge.Auth.Client.AuthGuard&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Concierge.Auth.Client.AuthGuard&version=2.0.0
                    
Install as a Cake Tool

Concierge.Auth.Client.AuthGuard

Two ASP.NET Core authentication schemes: ConciergeJwt verifies AuthService-issued human JWTs on incoming requests (contract §5) and WebSocket handshakes (§7); a named API-key scheme validates a presented key against a host-supplied loader. The only Concierge.Auth.Client.* package that references ASP.NET Core (FrameworkReference, never a Microsoft.AspNetCore.* PackageReference) or StackExchange.Redis.

The two-step check, in this exact order (contract §5)

  1. Signature/JWKS. RS256 only, via Concierge.Auth.Client.Keys's IJwksKeyProvider — this package never fetches JWKS itself. ClockSkew is hardcoded to 5 seconds (contract §5 item 3); ASP.NET's own default is 5 minutes, and that default is never reachable here — there is no option to change it. JsonWebTokenHandler, not the legacy JwtSecurityTokenHandler. Issuer and audience validation are off, because the token carries no iss/aud claim to validate against (contract §3.2: exactly {sub, jti, iat, exp}) — there is nothing to check.
  2. jti deny-list — MANDATORY, and only ever reached if step 1 already succeeded. EXISTS denylist:jti:<jti> in redis-cache (contract §3.4). Present → rejected. There is no in-memory fallback and no option to skip this check. If Redis is unreachable at request time, the request is rejected (fail closed) — never treated as "not denied". An in-memory alternative across multiple instances would silently fail to log a user out everywhere but one instance, which is worse than no deny-list at all because it looks like it works.

On success, exactly { userId, jti } (ConciergeRequestContext) is attached — never roles, scopes, permissions, tenant, email, or displayName. That is PolicyService's data, resolved separately from the cached assignment set (contract §3.2; integration-guide invariant 11). This package has no authorization opinion and no PolicyService dependency.

Install and register

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

services
    .AddConciergeAuthGuard()                    // required first call — wires up ASP.NET Core authentication
    .AddConciergeJwtBearer(configuration);       // binds "Concierge:AuthClient:TokenValidation"

Fails closed at this call, synchronously (decision D8): if ConciergeTokenValidationOptions.RedisConnectionString is not configured, AddConciergeJwtBearer throws OptionsValidationException immediately — an operator sees the misconfiguration at registration time, not on the first request that needed the deny-list. There is no way to configure this package without a deny-list store.

// appsettings.json
{
  "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 — see "Multiple schemes" below to target one explicitly. On success, ConciergeJwt attaches { userId, jti }; read it downstream with:

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

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

services
    .AddConciergeAuthGuard()
    .AddConciergeJwtBearer(configuration)               // default, since it's first
    .AddConciergeApiKey("partnerA", o => o.SecretLoader = ...)
    .AddConciergeApiKey("partnerB", o => o.SecretLoader = ...);

Target a specific scheme with [Authorize(AuthenticationSchemes = "partnerA")]; a custom scheme name is available for the JWT scheme too (AddConciergeJwtBearer(configuration, "MyJwtScheme")).

WebSocket handshake (contract §7)

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

var result = await webSocketAuthenticator.AuthenticateHandshakeAsync(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);

RunUntilExpiryAsync closes the socket once the token's exp passes — a long-lived connection must not outlive the credential that opened it.

Failure codes

  • TOKEN_INVALID — malformed, wrong key, wrong/disallowed algorithm (including none and HS256), or expired/not-yet-valid (outside the 5s clock skew).
  • TOKEN_DENIED — signature was valid, but the jti is on the deny-list.
  • DENYLIST_UNAVAILABLE — the deny-list store could not be reached; the request is 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).

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 396 8/24/2026
2.0.0 239 8/17/2026
1.0.0 109 8/17/2026