Darkwingduck
Darkwingduck

Reputation: 133

User Claims seem to be getting replaced somewhere along the pipeline

**Edit: If anyone has any clue how i can better ask or inform you guys about this problem please let me know.

So I am creating custom claims and trying to add them to my user. I see the claims in the User.Identity right after I add them and slightly down the line in the pipeline but by the time it gets to my Global.asax the User.Identity has lost all but one of my claims. I also think the user is changing from a claimsPrinciapl to a GenericPrincipal during the same time. I dont know if I am understanding this or explaining this very well. Not even sure what all code to post but I will post some below.

This is where my user is Authenticated and cookies and claims are create. Note i have been trying a lot of stuff so this might have some weird code:

    private AuthenticationResponse AuthenticateUserByService(string userName, string password, bool rememberMe)
    {
        Authenticator auth = new Authenticator(AppInfo.AuthServiceAddress, AppInfo.ClientId, AppInfo.Secret);
        AppInfo.rememberMe = rememberMe;
        AuthenticationResponse response = auth.Authenticate(userName, password);
        if (response.IsError)
        {
            // MessageBox.Show(response.ErrorDescription);
            return null;
        }

        if (response.AppUser == null)
        {
            //MessageBox.Show("No error or user!  Unknown reason.");
            return null;
        }
        var cookieHelper = new Helpers.CookieHelper();
        //FormsAuthenticationTicket authtick = new FormsAuthenticationTicket(1, response.AppUser.Username, DateTime.Now, DateTime.Now.AddSeconds(response.AppUser.ExpiresIn *2), true, response.AppUser.RefreshToken);
        var authtick = cookieHelper.CreateAuthTicket(response.AppUser, true);

        var authCookie = cookieHelper.CreateAuthCookie(authtick);
        Response.Cookies.Add(authCookie);

        var tokenCookie = cookieHelper.CreateTokenCookie(response.AppUser, true);
        Response.Cookies.Add(tokenCookie);

        // If caching roles in userData field then extract
        string[] roles = response.AppUser.Permissions.Select(x => x.PermissionName).ToArray(); // = authTicket.UserData.Split(new char[] { '|' });

        // Create the IIdentity instance
        IIdentity id = new FormsIdentity(authtick);

        var newIdent = new ClaimsIdentity(id);

        foreach (var item in roles)
        {
            newIdent.AddClaim(new Claim(ClaimTypes.Role, item));
        }
        ClaimsPrincipal cp = new ClaimsPrincipal(newIdent);           


        // Create the IPrinciple instance
        IPrincipal principal = cp; //new GenericPrincipal(id, roles);
        Thread.CurrentPrincipal = cp;
        AppDomain.CurrentDomain.SetThreadPrincipal(cp);
        // Set the context user 
        HttpContext.User = principal;
        //IOwinContext context = Request.GetOwinContext();
        //var authManager = context.Authentication;
        //authManager.SignIn(newIdent);

        this.AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, newIdent);


        return response;

In the above code, I can see my user and his claims right after I set the HttpContext.User.

Below is just me checking out the User to make sure it was successful:

   private AppUser AuthenticateUser(string userName, string password, bool rememberMe)
    {
        //bool userAuthenticated = false;
        AuthenticationResponse userAuthenticated = null;
        bool success = false;
        try
        {
            userAuthenticated = AuthenticateUserByService(userName, password, rememberMe);
            var c = User.Identity;
            success = !userAuthenticated.IsError;
        }
        catch { }
    }

At one point the claims disappeared by the time I set c to the user.

And i figured this might be important so below is where i create my cookies and tickets:

internal class CookieHelper
{
    internal FormsAuthenticationTicket CreateAuthTicket(AppUser appUser, bool isPersistent)
    {
        return new FormsAuthenticationTicket(
            1,
            appUser.Username,
            DateTime.Now,
            DateTime.Now.AddSeconds((appUser.ExpiresIn * 2)),
            isPersistent,
            appUser.RefreshToken == null ? "" : appUser.RefreshToken,
            FormsAuthentication.FormsCookiePath);
    }

    internal HttpCookie CreateAuthCookie(FormsAuthenticationTicket authTicket)
    {
        // Encrypt the ticket.
        string encAuthTicket = FormsAuthentication.Encrypt(authTicket);

        // Create the cookie.
        HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encAuthTicket);
        authCookie.Expires = authTicket.Expiration;

        return authCookie;
    }

    internal HttpCookie CreateTokenCookie(AppUser appUser, bool isPersistent)
    {

        // Create token ticket
        FormsAuthenticationTicket tokenTicket = new FormsAuthenticationTicket(
            1,
            appUser.Username,
            DateTime.Now,                
            DateTime.Now.AddSeconds(appUser.ExpiresIn),
            isPersistent,
            appUser.AccessToken);

        // Encrypt the ticket.
        string encTokenTicket = FormsAuthentication.Encrypt(tokenTicket);

        // Create the cookie.
        HttpCookie tokenCookie = new HttpCookie("Mellon", encTokenTicket);
        tokenCookie.Secure = false;
        tokenCookie.Name = "Mellon";
        //tokenCookie.Path = Request.ApplicationPath;
        tokenCookie.Expires = tokenTicket.Expiration;

        return tokenCookie;
    }
}

I feel like questions will need to be asked of me to get the right info for help. I am just lost and at this point my tunnel vision is killing me. Any insight or hints or jsut some love at this point would help. Thanks in advance.

Update

This is where I check if cookie is still valid and perform a refresh if its still valid.

 protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
        HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
        HttpCookie tokenCookie = Request.Cookies["Mellon"];

        if (authCookie == null)
        {
            FormsAuthentication.SignOut();
            HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
            return;
        }

        // Extract the forms authentication cookie
        FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);

        if (authTicket == null || authTicket.Expired)
        {
            FormsAuthentication.SignOut();
            HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
            return;
        }

        // Extract the forms authentication cookie
        //FormsAuthenticationTicket newAuthTicket;

        if (tokenCookie == null)
        {
            RefreshCookies(authTicket);
            return;
        }
        else
        {
            FormsAuthenticationTicket tokenTicket = FormsAuthentication.Decrypt(tokenCookie.Value);

            // If the access token is stil good, then continue on.
            if (tokenTicket.Expired)
            {
                RefreshCookies(authTicket);
                return;
            }
        }
        var tick = (FormsIdentity)HttpContext.Current.User.Identity;
        if (tick == null)
        {
            FormsAuthentication.SignOut();
            HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
            return;
        }

        if (authTicket.UserData != tick.Ticket.UserData) // .Ticket.UserData)
        {
            RefreshCookies(authTicket);
        }

    }

Basically what I have is my AuthToken which holds me refresh token and a second cookie that holds me AccessToken. Those are created in the AuthenticateUserByService method which gets all that info from our webapi and is returned in response.AppUser. So I can't use forms.setauthcookie because that would overwrite what is already in there.

Image proof of whats going on: Now you see them

Now you dont

Upvotes: 0

Views: 1356

Answers (2)

Darkwingduck
Darkwingduck

Reputation: 133

Basically what is happening is the claims are getting overwritten in my global.asax. My fix so far has been to just rebuild my claims in my global.asax.

Upvotes: 0

Tez Wingfield
Tez Wingfield

Reputation: 2251

As I said in my comment, it's rather tough to digest the snippets you have posted, So I'll break down into smaller logical chunks.

Let's start of with an Authentication Service Class:

Authentication Service calls the client repository and returns a User

 public class AuthenticationService
 {
    IUserRepository _userRepo;

    public AuthenticationService()
    {
      _userRepo = new UserRepository();
    }

    public User GetUser(string username, string password)
    {
       return _userRepo.FindByCredentials(username, password);
    }

   public User GetUserByUserName(string username)
    {
       return _userRepo.FindByUserName(username);
    }
 }

In the Global.asax we need to authenticate with pre-flight request.

      protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {
           //Check the request for a cookie
            var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];

            if (authCookie != null)
            {
                //Decrypt the Auth Cookie vale
                var ticket = FormsAuthentication.Decrypt(authCookie.Value);

                //Instantiate Auth Service
                var _authService = new AuthenticationService();

                //Get user by encrypted name stored in ticket
                var user = _authService.GetUserByUserName(ticket.Name);
                if (user != null)
                {
                    // Create a ClaimsIdentity with all the claims for this user.

                    Claim emailClaim = new Claim("Email", (!string.IsNullOrWhiteSpace(user.Email)) ? user.Email: "");
                    Claim AddressClaim = new Claim("Address", (!string.IsNullOrWhiteSpace(user.Address)) ? user.Address: "");
                    Claim userNameClaim = new Claim(ClaimTypes.Name, (!string.IsNullOrWhiteSpace(user.Username)) ? user.Username : "");

                   //Add claims to a collection of claims
                    List<Claim> claims = new List<Claim>
                    {
                        emailClaim ,
                        AddressClaim ,
                        userNameClaim 
                    };

                   //Create forms Identity
                    FormsIdentity formsIdentity = new FormsIdentity(ticket);

                   //Create Claims Identity
                    ClaimsIdentity claimsIdentity = new ClaimsIdentity(formsIdentity);

                   //Add Claims
                    claimsIdentity.AddClaims(claims);

                   //Create Claims Principal
                    ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

                    //Assign principal to current user
                    HttpContext.Current.User = claimsPrincipal;
                }
            }
        }

Login Controller:

        [HttpPost]
        [AllowAnonymous]
        public ActionResult Login(LoginModel model)
        {
            if (ModelState.IsValid)
            {
                    var user = _authService.GetUser(model.UserName, model.password);
                    if (user != null)
                    {
                        FormsAuthentication.SetAuthCookie(model.UserName,model.RememberMe);
                        return Redirect(model.ReturnUrl);                    }
                    }
            }

            ModelState.AddModelError("", "The user name or password provided is incorrect.");
            return View(model);

As I've said this is a naïve attempt, please consider a little more security, but this is working sln I've quickly put together and I can access the claims.

Having looked at your code, it feels that your just missing adding the Claims of the user.

Upvotes: 1

Related Questions