Custom user-driven claims for OWIN Identity

Home / Custom user-driven claims for OWIN Identity

Adding custom claims in .NET Identity, through OWIN, or otherwise is pretty straight forward.

But, what if we want to step outside of, or augment, the OAuth flow?


In the general authorization grant flow, after the user enters their credentials and they are validated, We basically create our ClaimsIdentity and just use the ‘AddClaim’ method that is available like so:

// Instantiate the identity
var identity = new ClaimsIdentity(new[] {
    new Claim(ClaimsIdentity.DefaultNameClaimType, username)
}, "MyAuthType");

// Add custom claims
identity.AddClaim(new Claim("custom1", "some custom claim"));
identity.AddClaim(new Claim("custom2", "some custom claim"));

// Sign in and let OWIN handle the redirecitons
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false}, identity);

That’s fine. However, if additional user input is desired to drive claims that the calling app may need, that are beyond any scope that could be derived programmatically or through a protected resource API, we need to intercept the flow (302 redirect) without breaking it.

In my use case, I had (2) drop downs from which I wanted users to make a selection and have those selections available as Claims. You may wonder why this couldn’t have been part of my login form. Well, the drop-downs request their data constrained by the user’s identity. My initial apprehension was that intercepting the OWIN middleware flow would break things. However, fortunately, it does not. Previously, my simple login controller action looked like this:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[OutputCache(NoStore = true, Location = OutputCacheLocation.None)]
public virtual ActionResult Login(LoginViewModel model, string returnUrl)
{
    ViewBag.ReturnUrl = returnUrl;
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    var validationResult = _validationService.ValidateCredentials(model.Username, model.Password);
    if (!validationResult.IsValid)
    {
        if (!string.IsNullOrWhiteSpace(validationResult.ErrorMessage))
        {
            ModelState.AddModelError("", validationResult.ErrorMessage);
        }
        else
        {
            ModelState.AddModelError("", "Invalid username or password.  Your account will be locked out after 3 failed attempts.");
        }
    }
    else
    {
        OwinSignin(model.Username, model.RememberMe, validationResult);
    }
    return View(model);
}

The OWIN configuration knows the path to Login as /Account/Login. After the “OwinSignin” method is called (which uses OWIN’s AuthManager), the middleware would typically intercept a 200 response and, instead, return the authorization code to whatever redirect_uri was passed in by the calling application.

By adding our own redirect, though, OWIN honors the redirect rather than attempting to intercept the response. To keep the OAuth flow intact, we can just replace “Login” in the Raw request Url with our controller action that displays the custom claim selection drop-downs and redirect. So, that’s all I needed to do immediately after the signin:

return Redirect(Request.RawUrl.Replace(MVC.Account.ActionNames.Login, "UserSelectClaims"));

For the controller action that we redirect to, the user is provided with a view with two drop-downs. Bear in mind, that with Cookie authenticaiton, technically, the user is already authenticated and the auth cookie exists at this point. In our POST method to the “UserSelectClaims” action, all we need to do is get the current ClaimsIdentity from the context and add the user’s selected values as Claims. Using the “ReturnUrl” that originally existed, if we redirect after POST to that, OWIN’s normal grant flow will proceed:

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
[OutputCache(NoStore = true, Location = OutputCacheLocation.None)]
public virtual ActionResult SelectMarket(string returnUrl, SelectionViewModel model)
{
    ViewBag.ReturnUrl = returnUrl;
    var identity = this.User.Identity as ClaimsIdentity;
    identity.AddClaim(new Claim("custom1", model.Custom1));
    identity.AddClaim(new Claim("custom2", model.Custom2));
    AuthManager.SignOut("MyAuthType");
    AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, identity);
    return Redirect(returnUrl);
}

Note how a SignOut is called immediately prior to calling SignIn with the identity. I did this to force OWIN to destroy/create the authentication cookie.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.