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:

ClaimMaps to
sub (then nameidentifier)TraxPrincipal.Id
name (then preferred_username, email)TraxPrincipal.DisplayName
role, roles, groupsTraxPrincipal.Roles
Everything elseTraxPrincipal.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

MethodPurpose
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).

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

ConditionResult
No session cookieAuthenticateResult.NoResult() (permits [AllowAnonymous])
Callback id-token invalid (signature, nonce, audience, lifetime)Handler fails the callback; user is not signed in
Callback valid, resolver returns nullcontext.Fail("OIDC id-token did not map to a known Trax principal.")
Callback valid, resolver throwscontext.Fail(exception)
Callback valid, resolver returns TraxPrincipalUser signed into the cookie scheme; subsequent requests authenticate against the cookie