AddTraxOidcAuth
NO WARRANTY. Trax auth is plumbing, not a security product. You are solely responsible for securing systems that use it. See API Security.
Registers the Trax OpenID Connect code-flow scheme (OidcDefaults.SchemeName), its session cookie (OidcDefaults.CookieSchemeName), the OidcDefaults.PolicyName authorization policy, the combined TraxAuthPolicy, IHttpContextAccessor, and a one-shot startup disclaimer log.
OIDC is a browser protocol. A challenge against the OIDC scheme issues a redirect to the identity provider; on callback, the handler validates the id-token and signs the user into the cookie scheme. Subsequent requests authorize against the cookie, not the OIDC scheme. For token-based API clients, use AddTraxJwtAuth instead.
Signatures
// Shortest path: authority + clientId, default scopes (openid, profile).
public static AuthenticationBuilder AddTraxOidcAuth(
this IServiceCollection services,
string authority,
string clientId);
public static AuthenticationBuilder AddTraxOidcAuth<TResolver>(
this IServiceCollection services,
string authority,
string clientId)
where TResolver : class, ITraxPrincipalResolver<OidcTokenInput>;
// Full control: client secret, extra scopes, callback paths, event handlers.
public static AuthenticationBuilder AddTraxOidcAuth(
this IServiceCollection services,
Action<OidcBuilder> configure);
public static AuthenticationBuilder AddTraxOidcAuth<TResolver>(
this IServiceCollection services,
Action<OidcBuilder> configure)
where TResolver : class, ITraxPrincipalResolver<OidcTokenInput>;Basic configuration
For public clients (SPA, native) with PKCE, the positional overload covers the common case:
services.AddTraxOidcAuth("https://login.example.com", "my-client-id");For confidential clients or extra scopes, use the builder:
services.AddTraxOidcAuth(oidc => oidc
.UseAuthority("https://login.example.com", "my-client-id")
.WithClientSecret(builder.Configuration["Oidc:ClientSecret"]!)
.AddScope("email"));PKCE is enabled by default on both paths.
Claim mapping
The default resolver reads standard OIDC claims:
| Claim | Maps to |
|---|---|
sub (then nameidentifier) | TraxPrincipal.Id |
name (then preferred_username, email) | TraxPrincipal.DisplayName |
role, roles, groups | TraxPrincipal.Roles |
| Everything else | TraxPrincipal.Claims (verbatim) |
For provider-specific mapping (Okta, Auth0, Entra, Cognito), supply a custom resolver:
public sealed class EntraOidcResolver : ITraxPrincipalResolver<OidcTokenInput>
{
public ValueTask<TraxPrincipal?> ResolveAsync(OidcTokenInput input, CancellationToken ct)
{
var oid = input.Principal.FindFirst("oid")?.Value;
if (oid is null) return new(default(TraxPrincipal));
var groups = input.Principal.FindAll("groups").Select(c => c.Value).ToArray();
return new(new TraxPrincipal(oid, input.Principal.FindFirst("name")?.Value ?? oid, groups,
PrincipalType: OidcDefaults.PrincipalType));
}
}
services.AddTraxOidcAuth<EntraOidcResolver>(oidc => oidc.UseAuthority("...", "..."));OidcBuilder methods
| Method | Purpose |
|---|---|
UseAuthority(string authority, string clientId) | Required. OIDC issuer URL and registered client id. |
WithClientSecret(string) | Confidential-client secret. Omit for public clients. |
AddScope(string) | Request additional scopes. openid and profile are registered by default. |
WithCallbackPath(string) | Override the default /signin-oidc. |
WithSignedOutCallbackPath(string) | Override the default /signout-callback-oidc. |
AllowHttpMetadata() | Permit non-HTTPS authority metadata. Dev/test only. |
DoNotSaveTokens() | Disable storing id/access tokens on the cookie. |
CustomizeOidcOptions(Action<OpenIdConnectOptions>) | Raw access to OIDC options (event handlers, protocol validator). Do not overwrite Events wholesale. |
CustomizeCookieOptions(Action<CookieAuthenticationOptions>) | Session cookie tweaks (expiration, SameSite, domain). |
Session cookie
The cookie is trax.oidc, HTTP-only, SameSite=Lax, Secure, with a sliding 8-hour expiration. Unauthenticated requests return 401 (not a redirect to /Account/Login), because this is an API-auth package. If you embed an MVC login page, override via CustomizeCookieOptions:
services.AddTraxOidcAuth(oidc => oidc
.UseAuthority("...", "...")
.CustomizeCookieOptions(cookie =>
{
cookie.LoginPath = "/login";
cookie.Events.OnRedirectToLogin = context =>
{
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
};
}));Protecting endpoints
Authorize against the cookie scheme (not the OIDC scheme):
app.UseTraxGraphQL(configure: endpoint => endpoint
.RequireAuthorization(OidcDefaults.PolicyName));
// Or the combined Trax policy to allow API-key / JWT / OIDC interchangeably:
app.UseTraxGraphQL(configure: endpoint => endpoint
.RequireAuthorization(TraxAuthClaimTypes.TraxAuthPolicy));Issue a sign-in challenge by challenging the OIDC scheme:
app.MapGet("/login", (HttpContext ctx) =>
Results.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
new[] { OidcDefaults.SchemeName }));Subscriptions
The session cookie authenticates the WebSocket upgrade for GraphQL subscriptions like any other request. The Trax schema accepts that upgrade only from origins the host serves: the endpoint's own host, or the origins allowed by AllowSocketOrigins(...), which default to your CORS default policy. If your SPA is on another origin, list it in the CORS default policy or pass it to AllowSocketOrigins(...). See Subscriptions.
Return Semantics
| Condition | Result |
|---|---|
| No session cookie | AuthenticateResult.NoResult() (permits [AllowAnonymous]) |
| Callback id-token invalid (signature, nonce, audience, lifetime) | Handler fails the callback; user is not signed in |
Callback valid, resolver returns null | context.Fail("OIDC id-token did not map to a known Trax principal.") |
| Callback valid, resolver throws | context.Fail(exception) |
Callback valid, resolver returns TraxPrincipal | User signed into the cookie scheme; subsequent requests authenticate against the cookie |