Claude
Skills
Sign in
Back

aspnetcore-authentication

Included with Lifetime
$97 forever

ASP.NET Core authentication middleware configuration including OpenID Connect, JWT Bearer, cookie authentication, authentication schemes, challenge/forbid flows, and external identity provider integration.

General

What this skill does


# ASP.NET Core Authentication

## When to Use This Skill

Use this skill when:
- Configuring OIDC authentication in an ASP.NET Core web application
- Setting up JWT Bearer authentication for an API
- Managing authentication schemes (cookies, OIDC, JWT, external providers)
- Implementing challenge, sign-in, sign-out, and forbid flows
- Debugging authentication failures (401s, redirect loops, claim mapping issues)
- Integrating with Duende IdentityServer as an OpenID Connect provider
- Configuring token validation parameters

## Core Principles

1. **Authentication ≠ Authorization** — Authentication establishes *who* the user is. Authorization (see `aspnetcore-authorization`) determines *what* they can do.
2. **Scheme-Based Architecture** — ASP.NET Core authentication is built around named schemes. Each scheme has a handler that knows how to authenticate, challenge, and sign out.
3. **Cookies for Web Apps, JWT for APIs** — Web applications use cookie authentication (with OIDC for login). APIs use JWT Bearer or introspection.
4. **Never Roll Your Own** — Use the built-in OIDC and JWT Bearer handlers. They handle nonce validation, key rotation, token validation, and dozens of edge cases.
5. **Claim Type Mapping Matters** — The OIDC handler maps JWT claim types to .NET claim types by default. Disable this for predictable claim names.

## Related Skills

- `aspnetcore-authorization` — Policy-based authorization after authentication
- `identityserver-configuration` — Server-side client and resource configuration
- `identityserver-sessions-providers` — Server-side sessions to reduce cookie size and maintain IdP-side data
- `oauth-oidc-protocols` — Protocol fundamentals underlying these handlers
- `token-management` — Automatic token refresh with Duende.AccessTokenManagement

Docs: https://docs.duendesoftware.com/identityserver/tokens/authentication

---

## Pattern 1: OIDC Authentication for Web Applications

The most common pattern — a server-rendered web app authenticating users via Duende IdentityServer:

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = "Cookies";
    options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
    options.Cookie.Name = "myapp";
    options.Cookie.SameSite = SameSiteMode.Lax;
    options.ExpireTimeSpan = TimeSpan.FromHours(8);
    options.SlidingExpiration = true;
})
.AddOpenIdConnect("oidc", options =>
{
    options.Authority = "https://identity.example.com";
    options.ClientId = "web.app";
    options.ClientSecret = "secret";
    options.ResponseType = "code"; // Authorization code flow

    // Map scopes to request
    options.Scope.Clear();
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("email");
    options.Scope.Add("api1");
    options.Scope.Add("offline_access"); // For refresh tokens

    // Save tokens in the authentication cookie
    options.SaveTokens = true;

    // Disable Microsoft's JWT claim type mapping
    options.MapInboundClaims = false;

    // Where to get additional user claims
    options.GetClaimsFromUserInfoEndpoint = true;

    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name",
        RoleClaimType = "role"
    };
});

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
```

### Critical Settings Explained

| Setting | Why | Default |
|---------|-----|---------|
| `MapInboundClaims = false` | Prevents renaming `sub` → `http://schemas.xmlsoap.org/.../nameidentifier` | `true` (maps) |
| `SaveTokens = true` | Stores access/refresh tokens in the cookie for later API calls | `false` |
| `GetClaimsFromUserInfoEndpoint = true` | Fetches full profile claims from userinfo | `false` |
| `ResponseType = "code"` | Authorization code flow (PKCE is automatic in .NET 7+) | `"code"` (.NET 7+; was `"code id_token"` in earlier versions) |

---

## Pattern 2: JWT Bearer Authentication for APIs

APIs validate access tokens issued by IdentityServer:

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://identity.example.com";
        options.Audience = "catalog-api"; // Must match ApiResource name

        options.MapInboundClaims = false; // Must be included

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = true,
            ValidAudience = "catalog-api",
            NameClaimType = "name",
            RoleClaimType = "role"
        };
    });

builder.Services.AddAuthorization();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

// Protect endpoints
app.MapGet("/products", () => Results.Ok())
    .RequireAuthorization();
```

### Multiple Audiences

When an API accepts tokens from multiple resources:

```csharp
options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateAudience = true,
    ValidAudiences = new[] { "catalog-api", "shared-api" }
};
```

---

## Pattern 3: Reference Token Introspection

For APIs that validate reference tokens (opaque tokens) instead of JWTs:

```csharp
builder.Services.AddAuthentication("Bearer")
    .AddOAuth2Introspection("Bearer", options =>
    {
        options.Authority = "https://identity.example.com";
        options.ClientId = "catalog-api";
        options.ClientSecret = "api-secret";
    });
```

> Install the `Duende.AspNetCore.Authentication.JwtBearer` package which supports both JWT and reference token validation, switching automatically based on the token format.

### Combined JWT + Reference Token Support

```csharp
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://identity.example.com";
        options.MapInboundClaims = false;

        // The Duende JWT handler can forward to introspection for reference tokens
        options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection");
    })
    .AddOAuth2Introspection("introspection", options =>
    {
        options.Authority = "https://identity.example.com";
        options.ClientId = "catalog-api";
        options.ClientSecret = "api-secret";
    });
```

---

## Pattern 4: Understanding Authentication Schemes

ASP.NET Core uses named authentication schemes. Each scheme is handled by a specific handler.

### Default Schemes

```csharp
builder.Services.AddAuthentication(options =>
{
    // Used for [Authorize] attribute and User.Identity
    options.DefaultScheme = "Cookies";

    // Used when authentication is required (401 → redirect to login)
    options.DefaultChallengeScheme = "oidc";

    // Used when access is denied (403)
    options.DefaultForbidScheme = "oidc";

    // Used when signing in (setting the cookie after OIDC callback)
    options.DefaultSignInScheme = "Cookies";

    // Used when signing out
    options.DefaultSignOutScheme = "oidc";
});
```

### The Authentication Flow

```
Request → [UseAuthentication] → Cookie handler reads cookie
                                  ├─ Valid cookie → User is authenticated
                                  └─ No cookie → User is anonymous
         [UseAuthorization]  → [Authorize] attribute checks
                                  ├─ Authenticated → proceed
                                  └─ Not authenticated → Challenge
                                       └─ OIDC handler redirects to IdentityServer
                                            └─ User logs in → callback → cookie created
```

---

## Pattern 5: Claim Type Mapping

By default, the Microsoft OIDC handler remaps JWT claims to XML-based .NET claim types. This causes confusion:

### The Mapping Problem

| JWT Claim | .NET Default Mapping | After `MapInboundClaims = false` |
|-----------|---------------------|------------------------

Related in General