惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
GbyAI
GbyAI
人人都是产品经理
人人都是产品经理
T
Tor Project blog
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
爱范儿
爱范儿
雷峰网
雷峰网
MongoDB | Blog
MongoDB | Blog
Vercel News
Vercel News
美团技术团队
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
L
LangChain Blog
IT之家
IT之家
博客园_首页
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
T
Tailwind CSS Blog
M
MIT News - Artificial intelligence
P
Proofpoint News Feed
Hacker News: Ask HN
Hacker News: Ask HN
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
量子位
Project Zero
Project Zero
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
T
Tenable Blog
aimingoo的专栏
aimingoo的专栏
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cyberwarzone
Cyberwarzone
C
CERT Recently Published Vulnerability Notes
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 叶小钗
Know Your Adversary
Know Your Adversary
L
Lohrmann on Cybersecurity
C
Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Schneier on Security
Schneier on Security
I
InfoQ
P
Privacy & Cybersecurity Law Blog
Spread Privacy
Spread Privacy
Martin Fowler
Martin Fowler
腾讯CDC
S
Security @ Cisco Blogs
F
Fortinet All Blogs

Duende Software Official Site

Duende Software Duende Software Duende Software Duende Software Duende Software Duende Software Duende Software Stop AI Bots from Wasting Your Server Core vs Extended Protocols in Duende IdentityServer v8: What You Get and When You Need More Your IdentityServer v8 Upgrade Checklist: A Quick Pre-Flight Guide Setting Up SAML Single Sign-On in ASP.NET with Duende IdentityServer Your Identity, Your Terms: Duende's Modular Identity Infrastructure and v8.x Release Duende Spring Launch '26: Identity Infrastructure That Expands With You SAML and OpenID Connect (OIDC): Coexistence, Not Competition The 9 Components of SAML You Need to Know, Ranked by Importance The Cost of NOT Implementing Financial-Grade Security Token Issuer Isolation: Why It Matters for Security and Compliance The Composable Identity Pattern: Build What You Need, Skip What You Don't Multi-Brand Identity: When Your Company Needs More Than One Face Post-Quantum Cryptography in .NET 10: A Practical Guide The field Keyword in C# 14: Write Less, Validate More The Real Cost of Build vs. Buy for Identity OAuth 2.1 Made Simple: The Only Flows You Need Beyond localhost: Multi-Instance ASP.NET Core Deployment with .NET 10 Harden Your .NET JSON Deserialization with System.Text.Json and JsonSerializerOptions.Strict ASP.NET Core Cookie Size Limits in Production: Causes and Fixes The Emergency Stop Button - Implementing Immediate Token Revocation in .NET 10 The 2025 OWASP Top 10 and IdentityServer Update Guidance for CVE-2026-40372 - ASP.NET Data Protection Why a Standard JWT Access Token Matters The Identity Governance Checklist You Wish You Had Six Months Ago The History and Future of SAML: Why a 20-Year-Old Protocol Still Matters The Cookie Apocalypse Already Happened Verify - Open Source Sponsorship Why Identity Is Infrastructure, Not a Feature Extending Duende IdentityServer Server-Side Sessions with Dynamic User Metadata Give Your AI Coding Assistant Duende Expertise with Agent Skills and MCP Server Triggering User Registration via OpenID Connect with Duende IdentityServer Improving .NET Security Code with C# 14 Property Extensions Developing Audit Logs with Duende IdentityServer Events Patch Releases: Addressing CVE-2026-26127 in Microsoft.BCL.Memory Client-Initiated Backchannel Authentication (CIBA) in ASP.NET Core 10 with Duende Identity Server Rate Limiting IdentityServer Endpoints It's Probably DNS - Can You Dig It? Security Lingo Explained: Encode vs Encrypt vs Hash Implementing Zero Trust with Resource Isolation Security Lingo Explained: JWT DPoP Security for .NET APIs with JwtBearer Extensions v1.0.0 Announcing the Duende IdentityServer4 Migration Analysis Tool BenchmarkDotNet - Open Source Sponsorship Security Lingo Explained: PAR Why Signing Key Rotation Matters in OpenID Connect and Duende IdentityServer Security Lingo Explained: OP Duende Year-End Review 2025 Security Lingo Explained: BCP Security Lingo Explained: DPoP Security Lingo Explained: Auth Secure frontend apps with the BFF Pattern Scaling with Duende IdentityServer, MCP, and AI Duende IdentityServer v7.4 is now available Duende BFFv4 is now available Securing OpenAPI and Swagger UI with OAuth in .NET 10 Building a Federation Gateway with Duende IdentityServer: Strategies and Considerations for Identity Orchestration
How Duende IdentityServer Filters Claims (And Why It Matters)
Khalid Abuhakmeh · 2026-06-18 · via Duende Software Official Site
Summary: Duende IdentityServer intentionally filters claims that appear in a user’s principal but are missing from the issued token. This design enforces the principle of least privilege, ensuring tokens contain only what was explicitly requested and helping keep token size manageable. The filtering occurs in AddRequestedClaims, which checks if a claim’s type is present in RequestedClaimTypes. This list is derived from the UserClaims defined on the IdentityResource objects associated with the scopes requested by the client. To ensure a claim, such as department, appears in the token, you must configure an IdentityResource to include that claim type and ensure the client requests the corresponding scope. Configuring resources is the secure and recommended approach rather than implementing a custom IProfileService to bypass the filter.

The other day, Anders Abel, founder of Sustainsys, and I were chatting about the parts of Duende IdentityServer that quietly do the right thing without anyone noticing. The conversation started from a recent post about ASP.NET Core cookie size limits, specifically Fix #7, which recommends using IdentityServer’s configuration system to control which claims end up in tokens.

Anders pointed out that the reason Fix #7 works so cleanly is a method most developers never look at directly: AddRequestedClaims. It trips up almost every developer at some point, and it’s easy to miss precisely because it works so well. Until you’re staring at a decoded token, wondering where your claim went.

If you’ve ever added a department or role claim to your user principal and then watched it vanish from the id_token, this post is for you.

“But the Claim Is Right There!”

Say you’ve added a custom claim (department, say) to your user during sign-in. You can see it on HttpContext.User.Claims. You’ve confirmed it’s on the principal. You request a token, decode it at jwt.me, and... it’s not there.

The natural assumption is that something is broken. The claim exists, so IdentityServer should pass it through. It doesn’t, and that’s intentional. The claim isn’t missing. It’s being filtered out, on purpose.

Inside DefaultProfileService

When IdentityServer needs to populate a token with user claims, it calls IProfileService.GetProfileDataAsync. If you haven’t registered a custom implementation, it uses DefaultProfileService, which ships with IdentityServer and looks like this:

Csharp

public virtual Task GetProfileDataAsync(ProfileDataRequestContext context, CancellationToken _)
{
    // ...
    context.LogProfileRequest(Logger);
    context.AddRequestedClaims(context.Subject.Claims);
    context.LogIssuedClaims(Logger);

    return Task.CompletedTask;
}
  • context.Subject.Claims — these are all the claims on the authenticated user’s principal. Everything.
  • But they’re passed through AddRequestedClaims rather than added directly to the token.
  • The logging calls are worth noting as well: LogProfileRequest logs, which claim types were requested, and LogIssuedClaims logs what was actually issued. If you turn on Debug-level logging, you can watch the filter in action.

AddRequestedClaims is where the filtering happens.

Important note: while Duende IdentityServer ships with DefaultProfileService, it is typically replaced in production scenarios with either Duende’s ASP.NET Core Identity implementation or a custom IProfileService written by the implementing team.

The Gatekeeper: AddRequestedClaims

AddRequestedClaims delegates to FilterClaims, and both are short enough to read in full:

Csharp

public static List<Claim> FilterClaims(this ProfileDataRequestContext context, IEnumerable<Claim> claims)
{
    ArgumentNullException.ThrowIfNull(context);
    ArgumentNullException.ThrowIfNull(claims);

    return claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}

public static void AddRequestedClaims(this ProfileDataRequestContext context, IEnumerable<Claim> claims)
{
    if (context.RequestedClaimTypes.Any())
    {
        context.IssuedClaims.AddRange(context.FilterClaims(claims));
    }
}

FilterClaims keeps only claims whose Type appears in context.RequestedClaimTypes. Everything else is dropped. AddRequestedClaims adds one more guard: if RequestedClaimTypes is empty (meaning nothing was requested), nothing gets through.

So your department claim isn’t missing from the token. It’s being filtered because "department" was never in RequestedClaimTypes. The filter is working as designed.

So Who Decides What’s “Requested”?

RequestedClaimTypes is populated by the claims pipeline before IProfileService is ever called. The source is your Identity Resource configuration, specifically the UserClaims property on each IdentityResource.

The chain works like this:

  1. A client requests scopes — for example, openid profile department_info
  2. IdentityServer resolves those scope names to IdentityResource objects
  3. Each IdentityResource has a UserClaims collection listing which claim types it covers
  4. All those claim types get flattened into RequestedClaimTypes

Internally, for the userinfo endpoint path, that flattening looks like this (from UserInfoResponseGenerator):

Csharp

var identityResources = resourceValidationResult.Resources.IdentityResources;
result = identityResources.SelectMany(x => x.UserClaims).Distinct();

That single line is the bridge between your scope configuration and the claims filter. If your claim type isn’t listed in any requested IdentityResource.UserClaims, it won’t appear in RequestedClaimTypes, and FilterClaims will drop it.

The mental model: scopes → identity resources → UserClaims → RequestedClaimTypes → FilterClaims → IssuedClaims.

graph LR
    A[Scopes] --> B[Identity Resources]
    B --> C[UserClaims]
    C --> D[RequestedClaimTypes]
    D --> E[FilterClaims]
    E --> F[IssuedClaims]

A note on id_token vs. the userinfo endpoint: When a client requests both an identity scope and an access token in the same request, IdentityServer suppresses identity claims from the id_token by default. They’re available via the userinfo endpoint instead. If you’re not seeing your claim in the id_token even after configuring your resources correctly, this is likely why. You can override this per-client with AlwaysIncludeUserClaimsInIdToken = true, or simply fetch the claims from the userinfo endpoint, which is the recommended approach. The AlwaysIncludeUserClaimsInIdToken is available for backward compatibility and is intended only for use with clients that do not support calls to the userinfo endpoint.

The Right Fix: Configure Your Resources

Now that you understand the pipeline, the fix is straightforward. If you want department in the token, declare an IdentityResource that includes it:

Csharp

new IdentityResource
{
    Name = "department_info",
    DisplayName = "Department Information",
    UserClaims = { "department" }
}

Then make sure the client requests the department_info scope. Once it does, department will appear in RequestedClaimTypes, pass through FilterClaims, and land in the token.

You’re telling IdentityServer: when a client asks for department_info, they want the department claim. The filtering follows from that configuration.

The Escape Hatch: Custom IProfileService

Sometimes you need more control. Maybe you’re prototyping, or you have a claim that should always be present regardless of what scopes were requested. In that case, you can implement IProfileService directly:

Csharp

public class CustomProfileService : IProfileService
{
    public Task GetProfileDataAsync(ProfileDataRequestContext context, CancellationToken ct)
    {
        // Still respect the filter for most claims
        context.AddRequestedClaims(context.Subject.Claims);

        // But always include department, regardless of what was requested
        var department = context.Subject.FindFirst("department");
        if (department != null)
        {
            context.IssuedClaims.Add(department);
        }

        return Task.CompletedTask;
    }

    public Task IsActiveAsync(IsActiveContext context, CancellationToken ct)
    {
        context.IsActive = true;
        return Task.CompletedTask;
    }
}

Register it in your DI container:

Csharp

builder.Services.AddIdentityServer()
    .AddProfileService<CustomProfileService>();

This example still calls AddRequestedClaims for the bulk of the claims, and only bypasses the filter for the specific claim it needs to force through. That’s a good pattern: use the escape hatch surgically, not as a wholesale replacement for the filtering behavior.

Adding claims directly to IssuedClaims without filtering means you’re responsible for not leaking sensitive data to clients that shouldn’t see it. The filter exists for a reason.

Security by Default

That reason is “least privilege for claims.” Tokens should only contain what was explicitly requested and configured. Without this filter, every claim on the user principal would appear in every token, including claims meant for internal use, sensitive attributes, or data irrelevant to the requesting client.

This is the same philosophy as scope-based access control: being authenticated doesn’t mean you get everything. It means you get what you asked for, and only what you asked for.

There’s a practical benefit too: smaller tokens. Tokens that travel in cookies or HTTP headers are subject to size constraints. Filtering out unrequested claims keeps tokens lean. As the cookie size limits post covers, this matters more than you’d think in production.

Hidden in Plain Sight

This is exactly the kind of thing Anders was talking about: a design decision that quietly protects you, but looks like a bug the first time you hit it.

Before you reach for a custom IProfileService, check your IdentityResource configuration. The claim you’re looking for is probably there on the principal, waiting to be requested. Add it to the right resource’s UserClaims, make sure the client requests that scope, and the filter will do the rest.