ASP.NET and WIF: Showing custom profile username as User.Identity.Name
I am building ASP.NET MVC application that uses external services to authenticate users. For ASP.NET users are fully authenticated when they are redirected back from external service. In system they are logically authenticated when they have created user profiles. In this posting I will show you how to force ASP.NET MVC controller actions to demand existence of custom user profiles.
Using external authentication sources with AppFabric
Suppose you want to be user-friendly and you don’t force users to keep in mind another username/password when they visit your site. You can accept logins from different popular sites like Windows Live, Facebook, Yahoo, Google and many more. If user has account in some of these services then he or she can use his or her account to log in to your site.
If you have community site then you usually have support for user profiles too. Some of these providers give you some information about users and other don’t. So only thing in common you get from all those providers is some unique ID that identifies user in service uniquely.
Image above shows you how new user joins your site. Existing users who already have profile are directed to users homepage after they are authenticated. You can read more about how to solve semi-authorized users problem from my blog postingASP.NET MVC: Using ProfileRequiredAttribute to restrict access to pages.
The other problem is related to usernames that we don’t get from all identity providers.
Why is IIdentity.Name sometimes empty?
The problem is described more specifically in my blog posting Identifying AppFabric Access Control Service users uniquely. Shortly the problem is that not all providers have claim called http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name. The following diagram illustrates what happens when user got token from AppFabric ACS and was redirected to your site.
Now, when user was authenticated using Windows Live ID then we don’t have name claim in token and that’s why User.Identity.Name is empty. Okay, we can force nameidentifier to be used as name (we can do it in web.config file) but we have user profiles and we want username from profile to be shown when username is asked.
Modifying name claim
Now let’s force IClaimsIdentity to use username from our user profiles. You can read more about my profiles topic from my blog posting ASP.NET MVC: Using ProfileRequiredAttribute to restrict access to pages and you can find some useful extension methods for claims identity from my blog posting Identifying AppFabric Access Control Service users uniquely.
Here is what we do to set User.Identity.Name:
- we will check if user has profile,
- if user has profile we will check if User.Identity.Name matches the name given by profile,
- if names does not match then probably identity provider returned some name for user,
- we will remove name claim and recreate it with correct username,
- we will add new name claim to claims collection.
All this stuff happens in Application_AuthorizeRequest event of our web application. The code is here.
protected void Application_AuthorizeRequest()
{
if (string.IsNullOrEmpty(User.Identity.Name))
{
var identity = User.Identity;
var profile = identity.GetProfile();
if (profile != null)
{
if (profile.UserName != identity.Name)
{
identity.RemoveName();
var claim = new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", profile.UserName);
var claimsIdentity = (IClaimsIdentity)identity;
claimsIdentity.Claims.Add(claim);
}
}
}
}
RemoveName extension method is simple – it looks for name claims of IClaimsIdentity claims collection and removes them.
public static void RemoveName(this IIdentity identity)
{
if (identity == null)
return;
var claimsIndentity = identity as ClaimsIdentity;
if (claimsIndentity == null)
return;
for (var i = claimsIndentity.Claims.Count - 1; i >= 0; i--)
{
var claim = claimsIndentity.Claims[i];
if (claim.ClaimType == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")
claimsIndentity.Claims.RemoveAt(i);
}
}
And we are done. Now User.Identity.Name returns the username from user profile and you can use it to show username of current user everywhere in your site.
Conclusion
Mixing AppFabric Access Control Service and Windows Identity Foundation with custom authorization logic is not impossible but a little bit tricky. This posting finishes my little series about AppFabric ACS and WIF for this time and hopefully you found some useful tricks, tips, hacks and code pieces you can use in your own applications.