18

I'm trying to extract an email address from HttpContext.User.Claims and I'd like to ask for help to come up with a better way to code this (maybe using LINQ?)

The way I'm doing it now seems very hacky.

var userClaimsList = HttpContext.User.Claims.ToList();

// List is of varying length but email is always 3rd from the bottom.
int emailPosition = userClaimsList.Count()-3; 
string searchString = "preferred_username: "; 

// dirtyEmail = "preferred_username: xyz@emailcom"
string dirtyEmail = userClaimsList[emailPosition].ToString();
string cleanEmail = dirtyEmail.Replace(searchString, "").ToLower().Trim();

I've tried the LINQ solutions recommended in another post but receive the error Operator == cannot be applied to operands of type 'Claim' and 'string'.

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
forcequitIO
  • 498
  • 2
  • 5
  • 18
  • 1
    *“but email is always 3rd from the bottom”* – That is a very dangerous assumption. Claims are identified by a claim type, so if you are looking for an email address, chances are that that claim has a specific claim type you should query for. So use that to get the value, e.g. using `HttpContext.User.FindFirstValue(emailClaimType)` – poke May 01 '18 at 17:33
  • Also note that the claim list is a list of `Claim` objects, not a string. So you doing `ToString()` will convert them to a string which will likely contain less information. You should instead work with the actual claim object directly. – poke May 01 '18 at 17:35
  • Yeah, I know. I hate making that assumption which was part of why I posted this question. – forcequitIO May 01 '18 at 17:35

2 Answers2

29

Claim objects are a bit more than just a simple string, and what you are looking at in the userClaimsList is a list of these claim objects.

Claims are mostly pairs of a claim type and a claim value, and when you look for certain information about a user, you should use the claim type to identity the user property you are looking for.

What you do in your code is assume that the claim you are looking for is the third to last, which by itself is already a dangerous assumption since you cannot be sure that this will always be the case: claims are generally considered unordered and you should look for them by type. And once you get the type, you then .ToString() it, which essentially reduces all the information the Claim type has down to a single string of the format claimType: claimValue. You can use that, but it’s really inefficient when the object itself as a much better way of accessing the claim value.

Since you are looking for the prefix "preferred_username: ", I assume that preferred_username is the claim type you are looking for. In that case, you could look up that claim like this:

var claim = HttpContext.User.Claims.First(c => c.Type == "preferred_username");
var emailAddress = claim.Value;

The use of First will throw an exception if a claim with that type was not found. If you don’t want that, you can use FirstOrDefault and then check whether claim is null.

There are also a few helper extensions that allow you to extract claims directly. In this case, you could use FindFirstValue on the user to get the claim value directly:

var emailAddress = HttpContext.User.FindFirstValue("preferred_username");

In case a claim with that type was not found, FindFirstValue will return null, so emailAddress could be null in this case.

poke
  • 369,085
  • 72
  • 557
  • 602
  • 1
    Thank you both for the education and examples. – forcequitIO May 01 '18 at 17:52
  • 1
    @CamiloTerevinto I was assuming that a claim like that always exists (hence using `First` which would throw otherwise). I edited in a few more details on that now. Thank you! – poke May 01 '18 at 17:54
  • AFAIK, there are no "required" claims, and "preferred_username" certainly isn't one. Not only this, but also the format of the type varies on whatever created them (for example, ASP.NET creates them using the `System.Security.Claims.ClaimTypes` class). – Camilo Terevinto May 01 '18 at 17:57
  • 1
    @CamiloTerevinto Sure, claims are only there if they were added to the identity. But how to handle their absense is up to the calling code that depends on them. If OP is within an environment where a claim of that type has to exist in order to continue, then throwing an exception when it isn’t there would be an acceptable reaction. But that’s not our decision here :) – poke May 01 '18 at 18:11
10

What you are doing is not only assuming something you cannot ensure, but also much harder than needed:

// note: HttpContext.User == User
var cleanEmail = User.FindFirst(ClaimTypes.Email)?.Value;

If the data is not on the email claim type, check the corresponding type and use that instead. In your case, it seems that it should be preferred_username:

var cleanEmail = User.FindFirst("preferred_username")?.Value;
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120