Principal get lost after one request - c#

I have the function AffiliateLogin in a controller that sets the Principal.
the row principal.User = user; is actually the one storing the Principal.
But after I redirect to another controller, and test my AuthorizeWithRolesAttribute attribute, the principal is reset.
This is one second after the login, you can see the red arrow:
this is the function that stores it.
What am I doing wrong?
Thanks
public JsonResult AffiliateLogin(string email, string password)
{
if (ModelState.IsValid)
{
Affiliate user = api.GetUserByCredencials<Affiliate>(email, password);
if (user != null)
{
IIdentity identity = new UserIdentity(true,user.Email);
UserPrincipal principal = new UserPrincipal(identity, new string[] {"Affiliate"});
principal.User = user;
HttpContext.User = principal;
return Json("Login success");
}
}
return Json("Fail To Login");
}

The principal property won't survive between web requests. You had to set it again in the next request after redirection.

If your doing doing custom authentication/forms authentication you should call
FormsAuthentication.SetAuthCookie
The next http from the browser with that cookie , Asp.net will process the cookie and set
the current claims principal. So you can check
var principal = ClaimsPrincipal.Current; //normally this reverts to Thread.CurrentPrincipal,
Here is a good place to learn a bit more
http://msdn.microsoft.com/en-us/library/system.security.claims.claimsprincipal.current

Related

Updated value of Claim is not reflecting in the subsequent requests

In my project, I am using token-based authentication and after a successful login, I store some user-specific values in user's token and to do this I have used Claims.
Below is the code I am using to store claims after login:
User user = new UserManager().GetUser(UserName, Password);
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, user.FullName),
new Claim(ClaimTypes.Email, user.Email),
new Claim("IsLocked", Convert.ToString(user.IsLocked))
};
AuthenticationProperties properties = CreateProperties(context.UserName);
ClaimsIdentity oAuthIdentity = new ClaimsIdentity(claims, Startup.OAuthOptions.AuthenticationType);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
As you can see in the above code that I have a claim to store IsLocked value of the user. As per the requirement, I need to prevent access of each API action from users whose account is locked. To do so, I have created a custom action filter and inside that, I use the value of IsLocked claim and thus prevent actions from being executed if user's claim value says that the user account is locked.
Below is the code of my custom action filter:
public class AllowActiveUsersAttribute : ActionFilterAttribute
{
public AllowActiveUsersAttribute()
{
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var identity = (ClaimsPrincipal)Thread.CurrentPrincipal;
if (Convert.ToBoolean(identity.Claims.Where(c => c.Type == "IsLocked").Select(c => c.Value).SingleOrDefault()))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
And then I use this custom Attribute on all Web API actions, like:
[AllowActiveUsers]
public async Task<IHttpActionResult> GetAccountDetails()
This code works perfectly and I get Unauthorized error when I log in with a locked account and then try to use any API endpoint.
In our system, we have some rules and breaking those rules can lock the users' account. When the account gets locked then the user shouldn't be able to access any API endpoint. So after the successful login (with an account that is not locked), if a user breaks any rule then his/her account should get locked immediately and after that he/she must not be able to use any API endpoints.
To do this, I added code to update the value of IsLocked claim and it successfully updates the claim value. But when I try to get the value of IsLocked claim in the custom action then I get the same old value instead of the new return value. Below is the code that I am using to update the claim value.
// check for the existing claim and remove it
var user = User as ClaimsPrincipal;
var identity = user.Identity as ClaimsIdentity;
var claim = (from c in user.Claims where c.Type == "IsLocked" select c).FirstOrDefault();
if (claim != null)
identity.RemoveClaim(claim);
// add new claim
identity.AddClaim(new Claim("IsLocked", Convert.ToString(true)));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
Can you please suggest how I can get the new value in the custom attribute so that if a user's account gets locked then from next requests, none of the API requests should be entertained?
That's cause you are still using the old/existing token which still don't have the updated value of IsLocked and thus the scenario. To resolve this, either the user has to start afresh by means of generating a new token which would have the updated value. Essentially a new access token has to be generated.
See this issue link for more understanding https://github.com/IdentityServer/IdentityServer3/issues/2783

ASP.NET Identity and Claim-based

How to use claims? For example, I want to set access to each page (resource) for each user. I understand, I can do it using roles, but as I understand, claim-based is more effectively. But when I try to create a claim, I see the following method:
userIdentity.AddClaim(new Claim(ClaimTypes.Role, "test role"));
first parameter of constructor of Claim class get ClaimTypes enum, which has many "strange" members like Email, Phone etc. I want to set that this claim and then check this claim to have access to certain resource. I'm on wrong way? How to do it?
From the code above, I am assuming you have already added the claim in startup class on authenticated of your provider as below.
context.Identity.AddClaim(new Claim("urn:google:name", context.Identity.FindFirstValue(ClaimTypes.Name))); // added claim for reading google name
context.Identity.AddClaim(new Claim("urn:google:email", context.Identity.FindFirstValue(ClaimTypes.Email))); // and email too
Once you have added the claims in startup, when the request is actually processed check if its a callback and if yes, read the claims as below(in IHttpHandler).
public void ProcessRequest(HttpContext context)
{
IAuthenticationManager authManager = context.GetOwinContext().Authentication;
if (string.IsNullOrEmpty(context.Request.QueryString[CallBackKey]))
{
string providerName = context.Request.QueryString["provider"] ?? "Google";//I have multiple providers so checking if its google
RedirectToProvider(context, authManager, providerName);
}
else
{
ExternalLoginCallback(context, authManager);
}
}
If its 1st call redirect to provider
private static void RedirectToProvider(HttpContext context, IAuthenticationManager authManager, string providerName)
{
var loginProviders = authManager.GetExternalAuthenticationTypes();
var LoginProvider = loginProviders.Single(x => x.Caption == providerName);
var properties = new AuthenticationProperties()
{
RedirectUri = String.Format("{0}&{1}=true", context.Request.Url, CallBackKey)
};
//string[] authTypes = { LoginProvider.AuthenticationType, DefaultAuthenticationTypes.ExternalCookie };
authManager.Challenge(properties, LoginProvider.AuthenticationType);
//without this it redirect to forms login page
context.Response.SuppressFormsAuthenticationRedirect = true;
}
And finally read the claims you get back
public void ExternalLoginCallback(HttpContext context, IAuthenticationManager authManager)
{
var loginInfo = authManager.GetExternalLoginInfo();
if (loginInfo == null)
{
throw new System.Security.SecurityException("Failed to login");
}
var LoginProvider = loginInfo.Login.LoginProvider;
var ExternalLoginConfirmation = loginInfo.DefaultUserName;
var externalIdentity = authManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
var pictureClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("picture"));
var pictureUrl = pictureClaim.Value;
LogInByEmail(context, email, LoginProvider); //redirects to my method of adding claimed user as logged in, you will use yours.
}
Claim doesn't set permission. It's used to verify you that "you are who you claim to be you are". These claims are identified by issuer, usually a 3rd party. See for example this article for description.
So, you should define which claims are necessary (who user should be) in order to access a certain page. Otherwise, using claim-based authorization will be same as using identity based or role based.

Custom MVC Authentication without SetAuthCookie()

Because of the requirements of my project, I'm wanting to provide custom authenticate for my MVC controller actions. Therefore, I will not be using SetAuthCookie().
Intially I set a cookie as follows;
string userData = EncDec.MakeString(user.Email + "|" + user.UserId);
//the Cookie and FormsAuthenticationTicket expiration date/time is the same
DateTime cookieExpiry = DateTime.Now.AddMinutes(AccountPage.MvcApplication.COOKIE_EXPIRY_MINUTES);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
user.UserName, // authenticated username
DateTime.Now, // issueDate
cookieExpiry, // expiryDate
false, // true to persist across browser sessions
userData, // can be used to store additional user data
FormsAuthentication.FormsCookiePath); // the path for the cookie
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
//create the cookie
HttpCookie cookie = new HttpCookie("ADV_" + Extensions.ControllerExtensionMethods.GetGuid(this), encryptedTicket);
cookie.Secure = true;
cookie.HttpOnly = true;
cookie.Expires = cookieExpiry;
Response.Cookies.Add(cookie);
The HttpCookie is being saved in the client browser with a encrypted FormsAuthenticationTicket.
Then within my controller actions, whenever I need to check and verify that the user is authenticated I call this method;
public static FormsAuthenticationTicket IsAuthenticated(string guid)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies["ADV_" + guid];
if (cookie != null)
{
string encryptedTicket = cookie.Value;
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encryptedTicket);
if (!ticket.Expired)
{
//if the user is authenticated and the cookie hasn't expired we increase the expiry of the cookie - keep alive
DateTime cookieExpiry = DateTime.Now.AddMinutes(AccountPage.MvcApplication.COOKIE_EXPIRY_MINUTES);
//create a new ticket based on the existing one
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(
ticket.Version, // ticket version
ticket.Name, // authenticated username
ticket.IssueDate, // issueDate
cookieExpiry, // expiryDate, changed to keep alive if user is navigating around site
false, // true to persist across browser sessions
ticket.UserData, // can be used to store additional user data
ticket.CookiePath); // the path for the cookie
string newEncryptedTicket = FormsAuthentication.Encrypt(newTicket);
//keep alive
HttpCookie newCookie = new HttpCookie("ADV_" + guid, newEncryptedTicket);
newCookie.Secure = true;
newCookie.HttpOnly = true;
newCookie.Expires = cookieExpiry;
HttpContext.Current.Response.Cookies.Set(newCookie);
return newTicket;
}
}
return null;
}
Every time the user is re-authenticated, I am increasing the time out of when the cookie will expire, so that the login is keep alive.
Everything seems to work fine, and the users are correctly authenticated, and if they aren't authenticated I redirect them to a login page, and they can't access methods if they aren't authenticated either.
My questions are:
Is this way of dealing with the authentication secure.
Is there anything I should be aware of, in terms of a security risk.
Thanks.
You basically need to look at creating a Custom Authentication Attribute.
I'm not going to provide the actual implementation here, but this will put you on the right path.
Here's the basic representation :-
public class GoogleAuthAttribute : FilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
IIdentity ident = filterContext.Principal.Identity;
if (!ident.IsAuthenticated || !ident.Name.EndsWith("#google.com"))
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
if (filterContext.Result == null || filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", "GoogleAccount"},
{"action", "Login"},
{"returnUrl", filterContext.HttpContext.Request.RawUrl}
});
}
}
}
I've took this from the Apress Book i'm currently reading on MVC5. If OnAuthentification fails, you set the Result property of the AuthenticationContext, this is then passed to the AuthenticationChallengeContext, where you can add your challenge code.
In my example, the user is redirected to the login page.
All you need t do, is place this AuthentificationAttribute on the Action Methods you require.
You should be able to build in or work your custome security code in to this.
You should really ask yourself if its a good idea to be adding custom security measures, as it can lead to more problems that you want.

How to get recently authenticated user?

I am working with MVC 3 and I have just implemented a wrapper for the FormsAuthenticationService.
Something similar to the following.
public void SignIn(string username, bool createPersistantCookie)
{
if (string.IsNullOrEmpty(username))
throw new ArgumentException("Value Cannot be null or empty", "username");
FormsAuthentication.SetAuthCookie(username, createPersistantCookie);
}
Reluctantly, I have gotten this to work, but now I am not quite sure how to get the information that I have stored.
Once the user is in my system, how can I now safely retrieve this information if I need to grab their UserID out of the database?
Based on the additional information provided, you want to store additional data with the FormsAuthentication ticket. To do so, you need first create a custom FormsAuthentication ticket:
Storing Data
Grab the current HttpContext (not worrying about testability)
var httpContext = HttpContext.Current;
Determine when the ticket should expire:
var expires = isPersistent
? DateTime.Now.Add(FormsAuthentication.Timeout)
: NoPersistenceExpiryDate; // NoPersistenceExpiryDate = DateTime.MinValue
Create a new FormsAuthentication ticket to hold your custom data.
var authenticationTicket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.Add(FormsAuthentication.Timeout),
isPersistent,
"My Custom Data String"); //Limit to about 1200 bytes max
Create your HTTP cookie
new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authenticationTicket))
{
Path = FormsAuthentication.FormsCookiePath,
Domain = FormsAuthentication.CookieDomain,
Secure = FormsAuthentication.RequireSSL,
Expires = expires,
HttpOnly = true
};
And finally add to the response
httpContext.Response.Cookies.Add(cookie);
Retrieving Data
Then you can retrieve your data on subsequent requests by parsing the stored authentication ticket...
Again, grab current HttpContext
var httpContext = HttpContext.Current
Check to see if the request has been authenticated (call in Application_AuthenticateRequest or OnAuthorize)
if (!httpContext.Request.IsAuthenticated)
return false;
Check to see if you have a FormsAuthentication ticket available and that it has not expired:
var formsCookie = httpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
if (formsCookie == null)
return false;
Retrieve the FormsAuthentication ticket:
var authenticationTicket = FormsAuthentication.Decrypt(formsCookie.Value);
if (authenticationTicket.Expired)
return false;
And finally retrieve your data:
var data = authenticationTicket.UserData;
You haven't actually stored a user id in the database. All the code that you've written does is store an authentication cookie on the users computer, either as a session cookie (not persistent) or as a persistent one.
When your page refreshes, it will get the cookie automatically, decode it, and populate the IPrincipal object which you access from the User.Current property of your controller.

ASP.NET MVC - Authorization Refactor

The problem i see with this code, is that it's going to be reused a lot; anything being edited/created by a authenticated user (except for Site administrators) will only have access to a their "studios" objects.
My question to you all; how would you re-factor this so the service layer can be abstracted away from the knowledge of the client. I intend to reuse the service layer in a stand-alone desktop application later.
Please shed some light on my erroneous ways! I greatly appreciate it.
AuthorizeOwnerAttribute.cs (AuthorizeAttribute)
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// Get the authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = httpContext.Request.Cookies[cookieName];
// If the cookie can't be found, don't issue the ticket
if (authCookie == null) return false;
// Get the authentication ticket and rebuild the principal & identity
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] userData = authTicket.UserData.Split(new[] { '|' });
int userId = Int32.Parse(userData[0]);
int studioID = Int32.Parse(userData[1]);
GenericIdentity userIdentity = new GenericIdentity(authTicket.Name);
WebPrincipal userPrincipal = new WebPrincipal(userIdentity, userId, studioID);
httpContext.User = userPrincipal;
return true;
}
Inside of my "User" Controller attach this attribute to any method that requires an owner
[AuthorizeOwner]
public ActionResult Edit(int Id)
{
IUser user = userService.GetById(HttpContext.User, Id);
return View(user);
}
Now, in my Service layer is where I'm checking the passed down IPrincipal if it has access to the object being requested. This is where it's getting smelly:
UserService.cs
public IUser GetById(IPrincipal authUser, int id)
{
if (authUser == null) throw new ArgumentException("user");
WebPrincipal webPrincipal = authUser as WebPrincipal;
if (webPrincipal == null) throw new AuthenticationException("User is not logged in");
IUser user = repository.GetByID(id).FirstOrDefault();
if (user != null)
{
if (user.StudioID != webPrincipal.StudioID) throw new AuthenticationException("User does not have ownership of this object");
return user;
}
throw new ArgumentException("Couldn't find a user by the id specified", "id");
}
I'm not sure I'd be storing the actual IDs in the cookie, that's a little too exposed. I'd be more inclined to use the Session hash to store that data thus keeping it on the server and not exposed.
I'd also use the Model (by passing the userID) to determine which objects to return, i.e. those that have a matching studioID. That way your controller would only ever have to call "GetObjects(int id)", if they don't have access to anything then you get a null or empty collection back. That feels cleaner to me.

Categories