.NET server quickstart
The .NET server SDK verifies auth4.dev access tokens the same way as the TypeScript server SDK. Use it in an ASP.NET Core API, a plain .NET game backend or a Unity dedicated server.
Before you start
You need three values from the console:
| Option | Console value | Example |
|---|---|---|
TenantId | Project ID | ten_abcdefghijklmnop |
Issuer | Issuer | https://auth.auth4.dev/t/ten_abcdefghijklmnop |
Audience | Audience (the client ID of the application calling your API) | cli_… |
Instead of Issuer you can set AuthOrigin, for examplehttps://auth.auth4.dev, and the SDK uses{AuthOrigin}/t/{TenantId}. Signing keys always come from{issuer}/.well-known/jwks.json, never from a URL in the token.
1. Add the SDK
The SDK is two projects in packages/sdk-dotnet:Auth4.Server (for .NET 8 and .NET Standard 2.1) andAuth4.Server.AspNetCore (for ASP.NET Core 8 and later). They aren't on NuGet yet, so reference the project from a copy of the repository:
<ItemGroup>
<ProjectReference Include="path/to/auth4/packages/sdk-dotnet/src/Auth4.Server.AspNetCore/Auth4.Server.AspNetCore.csproj" />
</ItemGroup>2. Protect an ASP.NET Core API
AddAuth4Bearer registers an authentication scheme. Requests without anAuthorization header pass through to anonymous endpoints. A missing or invalid token on a protected endpoint gets 401 with WWW-Authenticate: Bearer.
examples/dotnet/GameApi/Program.csbuilder.Services.AddAuthentication(Auth4BearerDefaults.AuthenticationScheme)
.AddAuth4Bearer(options =>
{
options.TenantId = Setting("AUTH4_TENANT_ID");
options.Issuer = Setting("AUTH4_ISSUER");
options.Audience = Setting("AUTH4_AUDIENCE");
});
builder.Services.AddAuthorization(options =>
{
// Guests can play, but only upgraded players can trade.
options.AddPolicy("upgraded", policy => policy.RequireAuthenticatedUser().RequireNonGuest());
});examples/dotnet/GameApi/Program.csapp.MapGet("/", () => Results.Ok(new { ok = true }));
app.MapGet("/profile", (HttpContext context) =>
{
var token = context.GetAuth4AccessToken()!;
return Results.Ok(new { player = token.Subject, guest = token.IsGuest, roles = token.Roles });
}).RequireAuthorization();
app.MapPost("/moderation/kick", () => Results.Ok(new { kicked = true }))
.RequireAuthorization(new Microsoft.AspNetCore.Authorization.AuthorizeAttribute { Roles = "moderator" });
var trade = app.MapPost("/trade", () => Results.Ok(new { traded = true }));
if (live)
{
trade.RequireAuthorization(new Microsoft.AspNetCore.Authorization.AuthorizeAttribute
{
AuthenticationSchemes = "Auth4Live",
Policy = "upgraded",
});
}
else
{
trade.RequireAuthorization("upgraded");
}- The user's name is the player's user ID (
sub).GetAuth4AccessTokenreturns the verified token with every claim. - Roles from the token become ASP.NET Core roles, so
[Authorize(Roles = "moderator")]andUser.IsInRolework. RequireNonGuestlets only upgraded players through, andRequireScoperequires a granted scope.
Run the example from the repository root:
AUTH4_TENANT_ID=ten_… \
AUTH4_ISSUER=https://auth.auth4.dev/t/ten_… \
AUTH4_AUDIENCE=cli_… \
dotnet run --project examples/dotnet/GameApi --urls http://127.0.0.1:5080
curl -H "Authorization: Bearer $ACCESS_TOKEN" http://127.0.0.1:5080/profileTo catch a wrong project ID or auth origin at startup, call DiscoverAsync(). It checks that the issuer's discovery document names the same issuer and key URL:
examples/dotnet/GameApi/Program.cs// Catch a wrong project ID or auth origin at startup instead of on the first request.
await app.Services.GetRequiredService<Auth4BearerClients>().Get().DiscoverAsync();Without ASP.NET Core
In a plain .NET backend or a Unity dedicated server, use Auth4ServerClient directly:
using Auth4.Server;
// One client per audience. Reuse it, so its signing-key cache is shared.
var auth4 = new Auth4ServerClient(new Auth4ServerOptions
{
TenantId = "ten_…",
AuthOrigin = "https://auth.auth4.dev", // or Issuer = "https://auth.auth4.dev/t/ten_…"
Audience = "cli_…",
});
try
{
var token = await auth4.ValidateAuthorizationHeaderAsync(authorizationHeader);
// token.Subject is the player's user ID. Key your game data on it.
}
catch (Auth4TokenException error) when (error.IsOutage)
{
// Signing keys or introspection were unavailable: report an outage, don't sign the player out.
}
catch (Auth4TokenException)
{
// Missing, malformed, expired or otherwise invalid: reject the request.
}For a Unity dedicated server, build Auth4.Server for .NET Standard 2.1 and add the DLL to the server build together with System.Text.Json, which Unity doesn't include. Game clients don't verify tokens; they use the Unity SDK.
3. Use the claims
| Property | Claim | Meaning |
|---|---|---|
Subject | sub | The player's user ID. It stays the same when a guest upgrades. |
TenantId | tenant_id | Your project ID. Always equal to the configured TenantId. |
ClientId | client_id | The application the token was issued to. |
Scopes | scope | Granted scopes. Check one with HasScope. |
SessionId | sid | The session ID, when the token belongs to a session. |
IsGuest | auth4_guest | true while the player hasn't upgraded their guest account. The next token after an upgrade says false. |
Roles | auth4_roles | The player's roles in this application. Check them with HasRole orHasAnyRole. See Roles and metadata. |
Metadata | auth4_meta | The player's flat metadata object. Empty when the player has none. |
ExpiresAt | exp | At most five minutes after issue. |
What the SDK checks
- The RS256 signature, with a key from the configured issuer, and the token type
at+jwt. iss,aud,tenant_id,expandnbf, with no clock tolerance by default. Allow up to 60 seconds of drift withClockTolerance.- The claim rules auth4.dev issues tokens with. A token with an unknown claim, a malformed role or a nested metadata value is rejected.
- ID tokens, tokens from other projects and console management tokens are always rejected.
ID tokens have their own method, ValidateIdTokenAsync. Use it only to confirm a sign-in for your own client ID, never to authorize an API call:
var idToken = await auth4.ValidateIdTokenAsync(rawIdToken, clientId: "cli_…", expectedNonce: nonce);Failures and key rotation
- Signing keys are cached for five minutes. A token signed with an unknown key ID triggers one key refresh, at most once every 30 seconds, so key rotation needs no restart.
- Failures throw
Auth4TokenException. Only the codeInvalidTokenmeans the token is bad.JwksUnavailable,JwksRefreshLimitedandIntrospectionUnavailableare outages, andIsOutageistrue. - The ASP.NET Core handler answers an outage with
503andRetry-After: 5instead of401, so players aren't signed out. SetOutageAsServiceUnavailable = falseto answer401instead.
Introspect mode
Introspect mode asks the project's introspection endpoint whether the token's session is still live after offline verification passes. It needs the client ID and secret of a confidential server client of the same project:
options.Introspection = new Auth4IntrospectionOptions
{
ClientId = configuration["AUTH4_SERVER_CLIENT_ID"],
ClientSecret = configuration["AUTH4_SERVER_CLIENT_SECRET"],
CacheTtl = TimeSpan.FromSeconds(5),
};The example adds it as a second scheme, so only sensitive endpoints pay for the extra request:
examples/dotnet/GameApi/Program.cs// Optional: a second scheme that also checks the session is still live, for sensitive endpoints.
var serverClientId = builder.Configuration["AUTH4_SERVER_CLIENT_ID"];
var serverClientSecret = builder.Configuration["AUTH4_SERVER_CLIENT_SECRET"];
var live = !string.IsNullOrEmpty(serverClientId) && !string.IsNullOrEmpty(serverClientSecret);
if (live)
{
builder.Services.AddAuthentication().AddAuth4Bearer("Auth4Live", options =>
{
options.TenantId = Setting("AUTH4_TENANT_ID");
options.Issuer = Setting("AUTH4_ISSUER");
options.Audience = Setting("AUTH4_AUDIENCE");
options.Introspection = new Auth4IntrospectionOptions
{
ClientId = serverClientId!,
ClientSecret = serverClientSecret!,
CacheTtl = TimeSpan.FromSeconds(5),
};
});
}- The endpoint is derived from the configured issuer, never from the token. It is available as
IntrospectionUrl. - An active answer is reused for
CacheTtl: 5 seconds by default, at most 60, and zero to check every request. An inactive answer is kept until the token expires. - If the endpoint can't be reached or answers with an error, verification fails closed with
IntrospectionUnavailable. - The SDK never logs the secret or includes it in
ToString(). Load it from your secret store, never from source code.
Create a server client in the console under Applications; see Server clients and their secrets.
SDK notes: packages/sdk-dotnet/README.md. Example: examples/dotnet/README.md. Tests: packages/sdk-dotnet/tests/Auth4.Server.Tests and tests/integration/auth4d-169.dotnet-sdk.test.ts.