Search code examples
oauth-2.0asp.net-coreopenid-connectopeniddict

Make the User.Identity include the email address from the access token payload


We are using OAuth via introspection to validate access tokens.

app.UseOAuthIntrospection(options =>
{
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
    options.Authority = "http://localhost:12345/";
    options.Audiences.Add("ResourceServer01");
    options.ClientId = "ResourceServer01";
    options.ClientSecret = "secret_secret_secret";
});

This works, mostly.

The Authorization Server response at connect/introspect is good.

{
  "active": true,
  "iss": "http://localhost:12345/",
  "sub": "797264b3-194c-483f-08fb-08d3cbab9158",
  "scope": "openid email roles",
  "iat": 1471998289,
  "nbf": 1471998289,
  "exp": 1472000089,
  "jti": "274cbb7f-9412-4d69-8c02-ca6a500b4a36",
  "token_type": "Bearer",
  "aud": [
    "ResourceServer01",
    "ResourceServer02"
  ],
  "email": "shaun@bigfont.ca",
  "AspNet.Identity.SecurityStamp": "4956a5c3-9efd-4f51-9746-43a187698e1e"
}

A request to the resource server gets past the Authorize attribute. This is also good.

[Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]
[HttpGet("message")]
public IActionResult GetMessage() {
    var identity = User.Identity as ClaimsIdentity;
    if (identity == null) {
        return BadRequest();
    }
    return Json(User);
}

The User though, does not contain the sub nor the email properties. It just looks like this:

{
    "claims": [
        {
            "issuer": "LOCAL AUTHORITY",
            "originalIssuer": "LOCAL AUTHORITY",
            "properties": {},
            "subject": {
                "authenticationType": "Bearer",
                "isAuthenticated": true,
                "actor": null,
                "bootstrapContext": null,
                "claims": []
            }
        }
    ]
}

How can we configure our resource server to include the sub and email properties in the claims?

Here is our code on GitHub.


Solution

  • The claims are likely there (if they're not, it's a bug in the introspection middleware), but JSON.NET is not very good at serializing Claim/ClaimsIdentity/ClaimsPrincipal, probably because these types have circular references (e.g Claim.Subject / ClaimsIdentity.Claims).

    Try using User.FindFirst(ClaimTypes.NameIdentifier)?.Value and User.FindFirst(ClaimTypes.Email)?.Value/ to confirm the subject identifier and the email address are there.

    If it works, consider returning a projection of your claims instead of a ClaimsPrincipal instance:

    return Json(
        from claim in User.Claims 
        select new { claim.Type, claim.Value }
    );
    

    Here is a screenshot the User in the Debug window.

    Visual Studio Code Debug Window showing the User