Missing Claims in the ASP.NET Core 2 OpenID Connect Handler?

The new OpenID Connect handler in ASP.NET Core 2 has a different (aka breaking) behavior when it comes to mapping claims from an OIDC provider to the resulting ClaimsPrincipal.

This is especially confusing and hard to diagnose since there are a couple of moving parts that come together here. Let’s have a look.

You can use my sample OIDC client here to observe the same results.

Mapping of standard claim types to Microsoft proprietary ones
The first annoying thing is, that Microsoft still thinks they know what’s best for you by mapping the OIDC standard claims to their proprietary ones.

This can be fixed elegantly by clearing the inbound claim type map on the Microsoft JWT token handler:

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

A basic OpenID Connect authentication request
Next – let’s start with a barebones scenario where the client requests the openid scope only.

First confusing thing is that Microsoft pre-populates the Scope collection on the OpenIdConnectOptions with the openid and the profile scope (don’t get me started). This means if you only want to request openid, you first need to clear the Scope collection and then add openid manually.

services.AddAuthentication(options =>
{
    options.DefaultScheme = "Cookies";
    options.DefaultChallengeScheme = "oidc";
})
    .AddCookie("Cookies", options =>
    {
        options.AccessDeniedPath = "/account/denied";
    })
    .AddOpenIdConnect("oidc", options =>
    {
        options.Authority = "https://demo.identityserver.io";
        options.ClientId = "server.hybrid";
        options.ClientSecret = "secret";
        options.ResponseType = "code id_token";
 
        options.SaveTokens = true;
                    
        options.Scope.Clear();
        options.Scope.Add("openid");
                    
        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name", 
            RoleClaimType = "role"
        };
    });

With the ASP.NET Core v1 handler, this would have returned the following claims: nbf, exp, iss, aud, nonce, iat, c_hash, sid, sub, auth_time, idp, amr.

In V2 we only get sid, sub and idp. What happened?

Microsoft added a new concept to their OpenID Connect handler called ClaimActions. Claim actions allow modifying how claims from an external provider are mapped (or not) to a claim in your ClaimsPrincipal. Looking at the ctor of the OpenIdConnectOptions, you can see that the handler will now skip the following claims by default:

ClaimActions.DeleteClaim("nonce");
ClaimActions.DeleteClaim("aud");
ClaimActions.DeleteClaim("azp");
ClaimActions.DeleteClaim("acr");
ClaimActions.DeleteClaim("amr");
ClaimActions.DeleteClaim("iss");
ClaimActions.DeleteClaim("iat");
ClaimActions.DeleteClaim("nbf");
ClaimActions.DeleteClaim("exp");
ClaimActions.DeleteClaim("at_hash");
ClaimActions.DeleteClaim("c_hash");
ClaimActions.DeleteClaim("auth_time");
ClaimActions.DeleteClaim("ipaddr");
ClaimActions.DeleteClaim("platf");
ClaimActions.DeleteClaim("ver");

If you want to “un-skip” a claim, you need to delete a specific claim action when setting up the handler. The following is the very intuitive syntax to get the amr claim back:

options.ClaimActions.Remove("amr");

If you want to see the raw claims from the token in the principal, you need to clear the whole claims action collection.

Requesting more claims from the OIDC provider
When you are requesting more scopes, e.g. profile or custom scopes that result in more claims, there is another confusing detail to be aware of.

Depending on the response_type in the OIDC protocol, some claims are transferred via the id_token and some via the userinfo endpoint. I wrote about the details here.

So first of all, you need to enable support for the userinfo endpoint in the handler:

options.GetClaimsFromUserInfoEndpoint = true;

If the claims are being returned by userinfo, ClaimsActions are used again to map the claims from the returned JSON document to the principal. The following default settings are used here:

ClaimActions.MapUniqueJsonKey("sub""sub");
ClaimActions.MapUniqueJsonKey("name""name");
ClaimActions.MapUniqueJsonKey("given_name""given_name");
ClaimActions.MapUniqueJsonKey("family_name""family_name");
ClaimActions.MapUniqueJsonKey("profile""profile");
ClaimActions.MapUniqueJsonKey("email""email");

IOW – if you are sending a claim to your client that is not part of the above list, it simply gets ignored, and you need to do an explicit mapping. Let’s say your client application receives the website claim via userinfo (one of the standard OIDC claims, but unfortunately not mapped by Microsoft) – you need to add the mapping yourself:

options.ClaimActions.MapUniqueJsonKey("website""website");

The same would apply for any other claims you return via userinfo.

I hope this helps. In short – you want to be explicit about your mappings, because I am sure that those default mappings will change at some point in the future which will lead to unexpected behavior in your client applications.

Posted in ASP.NET Core, IdentityServer, OpenID Connect, WebAPI | Leave a comment

End of IdentityServer3 Maintenance

Yesterday we made the decision to stop development and maintenance of IdentityServer3. This has a couple of reasons:

  • IdentityServer4 is the better OpenID Connect and OAuth 2 implementation in every aspect
  • ASP.NET Core 2 is now a mature platform
  • There is only that much time you can spend on OSS development and issue tracker support, so we decided to focus on current projects which are IdentityServer4, IdentityModel2 and oidc-client.js

This also applies to answering questions on the issue tracker – we recommend you either use StackOverflow for free support, or use our commercial support options.

Security vulnerabilities will be fixed ASAP of course. Please disclose them responsibly.

PS. If you are a customer or have an existing support contract, we will of course continue supporting you. If you want to start a new support contract for your IdentityServer3 deployment, please let us know.

Posted in IdentityServer, Uncategorized | 8 Comments

Using iOS11 SFAuthenticationSession with IdentityModel.OidcClient

Starting with iOS 11, there’s a special system service for browser-based authentication called SFAuthenticationSession. This is the recommended approach for OpenID Connect and OAuth 2 native iOS clients (see RFC8252).

If you are using our OidcClient library – this is how you would wrap that in an IBrowser:

using Foundation;
using System.Threading.Tasks;
using IdentityModel.OidcClient.Browser;
using SafariServices;
 
namespace iOS11Client
{
    public class SystemBrowser : IBrowser
    {
        SFAuthenticationSession _sf;
 
        public Task InvokeAsync(BrowserOptions options)
        {
            var wait = new TaskCompletionSource();
 
            _sf = new SFAuthenticationSession(
                new NSUrl(options.StartUrl),
                options.EndUrl,
                (callbackUrl, error) =>
                {
                    if (error != null)
                    {
                        var errorResult = new BrowserResult
                        {
                            ResultType = BrowserResultType.UserCancel,
                            Error = error.ToString()
                        };
 
                        wait.SetResult(errorResult);
                    }
                    else
                    {
                        var result = new BrowserResult
                        {
                            ResultType = BrowserResultType.Success,
                            Response = callbackUrl.AbsoluteString
                        };
 
                        wait.SetResult(result);
                    }
                });
 
            _sf.Start();
            return wait.Task;
        }
    }
}
Posted in .NET Security, IdentityModel, OAuth, OpenID Connect, Uncategorized, WebAPI | Leave a comment

Templates for IdentityServer4 v2

I finally found the time to update the templates for IdentityServer4 to version 2. You can find the source code and instructions here.

To be honest, I didn’t have time to research more advanced features like post-actions (wanted to do automatic restore, but didn’t work for me) and VSIX for Visual Studio integration. If anyone has experience in this area, feel free to contact me on github.

Also – more advanced templates are coming soon (e.g. ASP.NET Identity, EF etc…)

IS4 templates.gif

Posted in IdentityServer, OAuth, OpenID Connect, Uncategorized, WebAPI | Leave a comment

SAML2p Identity Provider Support for IdentityServer4

One very common feature request is support for acting as a SAML2p identity provider.

This is not a trivial task, but our friends at Rock Solid Knowledge were working hard, and now published a beta version. Give it a try!

 

Posted in .NET Security, IdentityServer, OpenID Connect, WebAPI | Leave a comment

New in IdentityServer4 v2: Simplified Configuration behind Load-balancers or Reverse-Proxies

Many people struggle with setting up ASP.NET Core behind load-balancers and reverse-proxies. This is due to the fact that Kestrel is often used just for serving up the application, whereas the “real HTTP traffic” is happening one hop earlier. IOW the ASP.NET Core app is actually running on e.g. http://localhost:5000 – but the incoming traffic is directed at e.g. https://myapp.com.

This is an issue when the application needs to generate links (e.g. in the IdentityServer4 discovery endpoint).

Microsoft hides the problem when running in IIS (this is handled in the IIS integration), and for other cases recommends the forwarded headers middleware. This middleware requires some more understanding how the underlying traffic forwarding works, and its default configuration does often not work for more advanced scenarios.

Long story short – we added a shortcut (mostly due to popular demand) to IdentityServer that allows hard-coding the public origin – simply set the PublicOrigin property on the IdentityServerOptions. See the following screenshot where I configured the value https://login.foo.com – but note that Kestrel still runs on localhost.

publicOrigin.png

HTH

Posted in ASP.NET Core, IdentityServer, Uncategorized, WebAPI | 10 Comments

IdentityServer4 v2

Wow – this was probably our biggest update ever! Version 2.0 of IdentityServer4 is not only incorporating all the feedback we got over the last year, it also includes the necessary updates for ASP.NET Core 2 – and also has a couple of brand new features. See the release notes for a complete list as well as links to issues and PRs.

The highlights (from my POV) are:

ASP.NET Core 2 support
The authentication system in ASP.NET Core 1.x was a left-over from Katana and was designed around the fact that no DI system exists. We suggested to Microsoft that this should be updated the next time they have the “luxury” of breaking changes. That’s what happened (see more details here).

This was by far the biggest change in IdentityServer (both from a config and internal plumbing point of view). The new system is superior, but this was a lot of work!

Support for the back-channel logout specification
In addition to the JS/session management spec and front-channel logout spec – we also implemented the back-channel spec. This is for situations where the iframe logout approach for server-side apps is either too brittle or just not possible.

Making federation scenarios more robust
Federation with external providers is a complex topic – both sign-in and sign-out require a lot state management and attention to details.

The main issue was the state keeping when making round–trips to upstream providers. The way the Microsoft handlers implement that is by adding the protected state on the URL. This lead to problems with URL length (either because Azure services default to 2KB of allowed URL length, e.g. Azure AD or because of IE who has the same restriction). We fixed that by including a state cache that you can selectively enable on the external handlers. This way the temporary state is kept in a cache and the URLs stay short.

Internal cleanup and refactoring
We did a lot of cleanup internally – some are breaking changes. Generally speaking we opened up more classes (especially around response generation) for derivation or replacement. One of the most popular requests was e.g. to customize the response of the introspection endpoint and redirect handling in the authorize endpoint. Oh btw – endpoints are now extensible/replaceable as well.

Support for the ASP.NET Core config system
Clients and resources can now be loaded from the ASP.NET config system, which in itself is an extensible system. The main use case is probably JSON-based config files and overriding certain settings (e.g. secrets) using environment variables.

Misc
We also updated our docs and the satellite repos like samples, EF, ASP.NET Identity and the quickstart UI. We gonna work on new templates and VS integration next.

Support
If you need help migrating to v2 – or just in general implementing IdentityServer – let us know. We provide consulting, support and software development services.

Last but not least – we’d like to thank our 89 contributors and everyone who opened/reported an issue and gave us feedback – keep it coming! We already have some nice additions for 2.x lined up. Stay tuned.

Posted in .NET Security, ASP.NET Core, IdentityServer, OpenID Connect, WebAPI | 4 Comments