Introduction to forms based authentication in ASP.NET MVC5 Part 5: Claims

Introduction

Claims in authorisation have received a lot of attention recently. Claims are simply key-value pairs where the key describes the type of claim, such as “first name” and the value provides the value of that claim, e.g. “Elvis”. Think of a passport which usually has a page with the photo and lots of claims: first name, last name, maiden name, expiry date etc. Those are all key-value pairs that describe the owner of the passport and provide reliable evidence that the person is really the one they are claiming to be.

I have a long series on claims on this blog. If you don’t anything about them then I recommend you at least go through the basics starting here. That series takes up claims in MVC 4. MVC 5 brings a couple of new features as far as claims are concerned.

Demo

We’ll be using the same demo application as before in this series so have it open in Visual Studio 2013.

As we said before authentication in MVC 5 is built using Katana components which are activated with extension methods on the incoming IAppBuilder object. These Katana components work independently of each other: e.g. you can turn on Google authentication at will as we saw in the previous part. At the same time you can even turn off the traditional cookie authentication, it won’t affect the call to app.UseGoogleAuthentication().

When we turned on Google authentication the consent screen stated that the application, i.e. the demo web app, will have access to the user’s email. That’s in fact a type of claim where the key is “email” or most probably some more complete namespace. Where’s that email? How can we read it?

Locate the method called ExternalLoginCallback in the AccountController. That method is fired when the user has successfully logged in using an external auth provider like Google or Facebook. The method will first need to extract the login information using…

var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();	

This login information contains the tokens, expiry dates, claims etc. As the login is taken care of by OWIN, MVC will need to tap into the corresponding Katana component to read those values. This is an example of a piece of logic outside OWIN that needs to find out something from it. The AccountController will need this information to handle other scenarios such as signing out. The key object here is the AuthenticationManager.

The AuthenticationManager which is of type IAuthenticationManager is extracted using the following private property accessor in AccountController:

private IAuthenticationManager AuthenticationManager
{
            get
            {
                return HttpContext.GetOwinContext().Authentication;
            }
}

The interface lies in the Microsoft.Owin.Security namespace. The AuthenticationManager is retrieved from the current OWIN context. The GetOwinContext extension method provides a gateway into OWIN from ASP.NET. It allows you to retrieve the request environment dictionary we saw in the series on OWIN:

HttpContext.GetOwinContext().Request.Environment;

I encourage you to type ‘HttpContext.GetOwinContext().’ in the editor and look through the available choices with IntelliSense. The Authentication property exists so that ASP.NET can read authentication related information from the OWIN context as all that is now handled by Katana components.

If you look further down in the code you’ll see the SignInAsync method. The method body shows how to sign users in and out using an external cookie-based login:

AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);

Now that we know a little bit about the authentication manager we can return to the ExternalLoginCallback method. Let’s see what the GetExternalLoginInfoAsync method returns in terms of login information. Add a breakpoint within the method and start the application. Click the Log in link and sign in with Google. Code execution stops within the method. Inspect the contents of the “loginInfo” variable. It doesn’t contain too much information: the user name, the provider name – Google – and a provider key. So there’s nothing about any provider specific related claims such as the user’s email address.

Note that the GetExternalLoginInfoAsync method only provides an object which includes properties common to all providers. The list is not too long as we’ve seen. However, the method cannot know in advance what the Google auth provider will provide in terms of claims. The list of available data will be different across providers. Some providers may provide a more generous list of claims than just an email: a URL to the user’s image, the user’s contacts, first and last names etc. Insert the following line of code above the call to GetExternalLoginInfoAsync:

AuthenticateResult authenticateResult = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);

Leave the breakpoint as it is and restart the application. Sign in with Google and inspect the authenticateResult variable. You’ll see that it provides a lot more information than the login info above. The claims are found
within the Identity property:

Claims from Google authentication

You can see that the claim types are identified in the standard URI way we saw in the series on claims. You can query the Claims collection to see if it includes the email claim. If your application explicitly requires the email address of the user then make sure to indicate it when you set it up with Google or any other auth provider.

You can save the email of the user in at least two ways:

  • Temporarily in the session using the ExternalLoginConfirmationViewModel object further down in the ExternalLoginCallback method. That view model doesn’t by default include any property for emails, you’ll need to extend it
  • In the database using the UserManager object we saw before in this series

Let’s see how we can achieve these. Locate the ExternalLoginConfirmationViewModel object in AccountViewModels.cs and extend it as follows:

public class ExternalLoginConfirmationViewModel
{
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

	public string EmailFromProvider { get; set; }
}

Add the following method to AccountController.cs to read the email claim from the claims list:

private string ExtractEmailFromClaims(AuthenticateResult authenticateResult)
{
	string email = string.Empty;
	IEnumerable<Claim> claims = authenticateResult.Identity.Claims;
	Claim emailClaim = (from c in claims where c.Type == ClaimTypes.Email select c).FirstOrDefault();
	if (emailClaim != null)
	{
		email = emailClaim.Value;
	}
	return email;
}

You can read this value in the body of ExternalLoginCallback and store it in a ExternalLoginConfirmationViewModel like this:

public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
	AuthenticateResult authenticateResult = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);

        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();			
         if (loginInfo == null)
            {
                return RedirectToAction("Login");
            }

            // Sign in the user with this external login provider if the user already has a login
            var user = await UserManager.FindAsync(loginInfo.Login);

			string emailClaimFromAuthResult = ExtractEmailFromClaims(authenticateResult);

            if (user != null)
            {
                await SignInAsync(user, isPersistent: false);
                return RedirectToLocal(returnUrl);
            }
            else
            {
                // If the user does not have an account, then prompt the user to create an account
                ViewBag.ReturnUrl = returnUrl;
                ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
                return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel 
					{ UserName = loginInfo.DefaultUserName, EmailFromProvider = emailClaimFromAuthResult });
     }
}

This page redirects to a View called ExternalLoginConfirmation when the user first signs up in the “else” clause. Locate ExternalLoginConfirmation.cshtml in the Views/Account folder. You can use the incoming model to view the extracted email claim:

<p>
    We've found the following email from the login provider: @Model.EmailFromProvider
</p>

I’ve deleted all rows in the AspNetUsers table so that I can view this extra information. Also, clear the cookies in your browser otherwise Google will remember you. You can also run the application from a brand new browser window. The email was successfully retrieved:

Found Google email claim

We can store the email claim in the database within ExternalLoginCallback as follows:

if (user != null)
{
        await SignInAsync(user, isPersistent: false);
        IList<Claim> userClaimsInDatabase = UserManager.GetClaims<ApplicationUser>(user.Id);
	Claim emailClaim = (from c in userClaimsInDatabase where c.Type == ClaimTypes.Email select c).FirstOrDefault();
	if (emailClaim == null)
	{
        	IdentityResult identityResult = UserManager.AddClaim<ApplicationUser>(user.Id, new Claim(ClaimTypes.Email, emailClaimFromAuthResult));
	}
				
        return RedirectToLocal(returnUrl);
}

First we check the claims stored in the database using the UserManager.GetClaims method. Then we check if the email Claim is present. If not then we add it to the database. The identityResult helps you check the result of the operation through the Errors and Succeeded properties.

The claims by default end up in the AspNetUserClaims table:

Email claim saved in the aspnetuserclaims table

You can of course use the AspNetUserClaims table to store any kind of claim you can think of: standard claims found in the ClaimTypes list or your own custom ones, such as http://mycompany.com/claims/customer-type.

You can view the list of posts on Security and Cryptography here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

9 Responses to Introduction to forms based authentication in ASP.NET MVC5 Part 5: Claims

  1. Andras – great post. One question – how do I extract claims if I use a WEB API as the one generating Bearer Tokens? I do not see my claims coming back in the Owin context at all.

    • Andras Nemes says:

      Shirish,

      You mean if you use a Web API interface for the login? Have you had a look at this post?

      //Andras

      • I saw that Post and I am able to Get an Access Token back to the UI (MVC Controller). But if I add Claims to the Authentication Ticket in the WEB API, I am not able to extract those from the Token. It does not matter what Principal Object I set in the Web API (Request, Owin Response etc), I only see Generic Principal on the return call. I tried the Claims Auth Manager, but still no luck. So is it possible to extract the claims I set in WebAPI in the MVC client at all?

      • Andras Nemes says:

        Can you upload your solution to GitHub so that I can take a look?
        //Andras

  2. Easier to just do it here if you don’t mind.

    1. Use your WebAPI Poject to generate a token. Set the claims within the token (GrantResourceOwnerCredentials)

    using (var userManager = _userManagerFactory){
    SomeIdentityUser user;
    user = await userManager.FindAsync(context.UserName, context.Password);
    if (user == null)
    {
    context.SetError(“invalid credentials”, “The user name or password is incorrect.”);
    return;
    }
    // set claims
    ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,context.Options.AuthenticationType);
    var properties = CreateProperties(user.UserName);
    var ticket = new AuthenticationTicket(oAuthIdentity, properties);
    oAuthIdentity.BootstrapContext = oAuthIdentity;
    context.OwinContext.Request.User = new ClaimsPrincipal(oAuthIdentity); // set the user identity
    context.Request.User = new ClaimsPrincipal(oAuthIdentity);
    context.OwinContext.Response.Context.Authentication.User = new ClaimsPrincipal(oAuthIdentity);
    context.Validated(ticket);
    context.Request.Context.Authentication.SignIn(properties, oAuthIdentity); // not sure if I need this line
    }

    2. Add a MVC project to the solution.
    3. Call Token generation and get back token

    public async Task Login(LoginViewModel model, string returnUrl)
    {
    if (ModelState.IsValid)
    {
    try
    {
    var httpClient = new WebClient(); ;
    httpClient.Headers.Add(“Accept”, “application/json”);
    httpClient.Headers.Add(“Content-Type”, “application/x-www-form-urlencoded”);
    var strContent = String.Format(“grant_type=password&username={0}&password={1}”, model.Email, model.Password);
    byte[] byteArray = Encoding.ASCII.GetBytes(strContent);
    byte[] respContext = httpClient.UploadData(“http://localhost:55337/api/token”, “POST”, byteArray);
    if (respContext != null)
    {
    var response = HttpContext.GetOwinContext().Response.Context;
    if (response.Response.StatusCode == 200)
    {
    var stringContent = Encoding.ASCII.GetString(respContext);
    JObject jObject = JObject.Parse(stringContent);
    var token = jObject.SelectToken(“access_token”);
    var issued = jObject.GetValue(“.issued”);
    var expires = jObject.GetValue(“.expires”);
    var bytes = Encoding.UTF8.GetBytes(token.ToString());

    // how do I get claims that I set in API here?? The Principal object is always generic principal and i do not see the claims identity i previously set here
    }
    }
    }
    catch (Exception exception)
    {
    var test = exception.Message;
    }
    return RedirectToLocal(returnUrl);
    }
    return View(model);
    }

    4. Now how do I get access to the claims I set in the API in MVC project.

    • Andras Nemes says:

      I’ve had no luck either, I get the same result as you. I even tried to set the User from MVC and read it in Web API to simulate an initial set of claims but request.owincontext.user is null. I’ve also tried to set the claims principal, add an identity etc., to no avail. The code seems logical so I’m not sure what’s wrong.
      I’ll test some more tomorrow evening before giving up though. Let me know if you find a solution in the meantime.
      //Andras

  3. Andras,

    Thanks for looking at this. I had used Identity Server with JWT Tokens in a previous assignment and the values would come thru correctly. But there are Certificates involved there. So maybe I have to go thru Identity Server code to see if anything can be used in this use case. Will update you as I go along – may not be right away. Thanks again.

  4. Andras – I found the solution. Look at this site — http://stackoverflow.com/questions/21805755/using-oauth-tickets-across-several-services. Look at answer 4. I was able to take that code and in the oauthstartuptions use this line AccessTokenFormat = new SecureTokenFormatter(PublicClientId — use any string).
    use the same class in the mvc client and the line var test = tokenProvider.Unprotect(token.ToString()); will give you the claims. let me know how it goes.

    • Andras Nemes says:

      Shirsh, thanks for coming back on this issue. I’ve got it to work as well. I still find it strange that we need to go through this AES decryption procedure.
      Thanks again, Andras

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

Elliot Balynn's Blog

A directory of wonderful thoughts

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT BEST PRACTICES WITH MICROSOFT STACK & ANGULAR

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: