Asp.net MVC when to reload claims when lost - asp.net

I have added a few custom claims when logging in.
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
var accesses = _roleAccessRepository.GetAssignedAccess(roleId);
foreach (var access in accesses)
{
identity.AddClaim(new Claim("AccessCode", access));
}
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
They work fine until I recently found that after certain inactivity, I assume of more than 30 minutes, these claims are lost. However the user is still logged in.
How do I reload these claims when the user is still logged in?
Update: How it occured.
I logged in to a user and then after roughly 30 minutes opened it again, and logged out of it. Instead of being logged out it refreshed my page, with no claims.

You are facing SecurityStampValidator in action. In the standard MVC5 template from VS2013/VS2015 there is a file App_Start\Startup.Auth.cs that has these lines:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
You need to look at SecurityStampValidator.OnValidateIdentity - this method regenerates cookie every 30 minutes (in default configuration). And it does not preserve claims added outside of user.GenerateUserIdentityAsync(manager).
So you need to find ApplicationUser class and modify GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) method to include your claims. And don't add claims anywhere else.

Related

Asp.NET Identity setting persistent cookie to expire after specific time

I have written a following code like below to refresh user roles after they subscribed to my website like following:
private void RefreshUserRoles()
{
var AuthenticationManager = HttpContext.GetOwinContext().Authentication;
var Identity = new ClaimsIdentity(User.Identity);
Identity.RemoveClaim(Identity.FindFirst(ClaimTypes.Role));
Identity.AddClaim(new Claim(ClaimTypes.Role, "Subscriber"));
AuthenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(
new ClaimsPrincipal(Identity),
new AuthenticationProperties { IsPersistent = true}
);
}
Please note this line of code:
AuthenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(
new ClaimsPrincipal(Identity),
new AuthenticationProperties { IsPersistent = true}
);
After user comes back to my website I set the cookie to be persistent, but I forgot to set the expiration for this cookie. I should set this cookie to last for 30 minutes, after which user should be asked to re-log onto the system.
Since some users never re-log on website, this leaves me with an issue, now I need to reset all users cookies in their browsers when they access the website and change their role if they cancelled the subscription. I noticed some users cancelled their subscription and never relogged but yet they still are able to use features on my website...
So my questions are:
How to set expiration of cookie to 30 minutes after which user will be asked to re-log onto the system.
How to Setup to re-check users cookies if they haven't expired in a long time so that I can reset their cookies to regular user if they cancelled subscription?
Here is the ConfigureAuth method I was talking about:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Controller/Action"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
That's how I have it set and it works for me.

GetExternalLoginInfoAsync() loginInfo return null - but only after a few hours

I'm using Strava as my external login provider (I assume this is not related to Strava, could be google or facebook also) After running for a few hours / days or even weeks GetExternalLoginInfoAsync return null. I've read a bunch of other questions with the same problem, but did not find a solution. I post my entire ConfigureAuth method, just in case I did something wrong with the order.
If you have a strava account you could probably experience the problem here: fartslek.no/Account/Login
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
CookieManager = new SystemWebCookieManager()
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseStravaAuthentication( new StravaAuthenticationOptions{
ClientId="XXX",
ClientSecret= "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
});
}
I'm using this https://github.com/Johnny2Shoes/Owin.Security.Strava to get StravaAuth.
When it stop working a azure reset is not enough, but if I do a new deploy everything works for a while.
I'm using Owin 3.0.1 and Mvc 5.2.3
I had the same problem. After googling a little, I've discovered this is a known bug in Owin, because of the way they handle cookies.
This issue was submitted to Katana Team, but it looks they won't fix it at all. There are many workarounds for this, but this was the simplest I could find:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
ControllerContext.HttpContext.Session.RemoveAll();
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
See this question for more details about this bug, and let me know if this works well for you.

Asp.net Identity 2 Account single entry

How to make asp.net identity account single entry?
A user who connects the system and when another person makes entry into that account with same email. I want to auto sign out the previous user.
With your claims? or With your DefaultCookie?
waiting for your help..
You could set the validation interval to 0 so every time the database will be checked for cookie validation:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromSeconds(0),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
And, when a user signs in, change his SecurityStamp. This will cause every existing cookie to be invalidated:
UserManager.UpdateSecurityStamp(userId);

Tracking daily user visits

I have an Asp.Net MVC 5 website. I want to track how many consecutive days a user has visited my website. Currently I have a code which tracks this in Login action but I can't track the users who've visited the website and have checked "remember me" in the login page. Needless to say, I'm looking for a way to achieve this with minimum possible performance hit.
try Like this ,
When you track user login you might be storing users IP address or User name into database.
You can save date and time along with IP and User Name.
from this you can track the users who've visited the website.
Go to Models folder -> IdentityModels.cs
In ApplicationUser class find GenerateUserIdentityAsync method. It is called both for straignt signin and cookies one.
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
Cookies call goes from StartupAuth.cs class Startup -> ConfigureAuth -> app.UseCookieAuthentication -> Provider -> regenerateIdentity -> user.GenerateUserIdentityAsync
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),
}
});

Identity cookie loses custom claim information after a period of time

I am storing custom claims, such as the user's real name, in the ASP.NET Identity cookie to avoid unnecessary database queries on every request. At least that's what I assume this code is doing:
var identity = await user.GenerateUserIdentityAsync(UserManager);
identity.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName)));
// etc.
AuthenticationManager.SignIn(new AuthenticationProperties {IsPersistent=true}, identity);
This works fine, and I can retrieve these claims with:
private static string GetClaim(string claimType)
{
var identity = (ClaimsPrincipal) Thread.CurrentPrincipal;
var claim = identity.Claims.SingleOrDefault(o => o.Type == claimType);
return claim == null ? null : claim.Value;
}
The identity.Claims property contains the following claims, as expected:
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: ced2d16c-cb6c-4af0-ad5a-09df14dc8207
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: me#example.com
http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider: ASP.NET Identity
AspNet.Identity.SecurityStamp: 284c648c-9cc7-4321-b0ce-8a347cd5bcbf
http://schemas.microsoft.com/ws/2008/06/identity/claims/role: Admin
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname: My Name
The trouble is that after some time (usually several hours), my custom claims seem to disappear - in this example, givenname no longer exists in the enumeration. The user is still authenticated, and all the default claims are still there.
What's going on, and how can I fix this? The only thing I can think of is that the cookie is expiring and being re-issued behind the scenes, but I don't know why (or if) that would happen.
Yes, the issue is most likely the cookie getting expired. Since you didn't add the custom claims to the user's claims in the database, they are lost on refresh since you aren't adding the claim inside the method being called. You can either add the claim via:
userManager.AddClaim(user.Id, new Claim(ClaimTypes.GivenName, user.FirstName));
or you can move this inside of the method that's called when the cookie is regenerated (by default its user.GenerateUserIdentityAsync).
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider {
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
In Net5 you can get old cookie Custom Claim and add to new cookie like this,
builder.Services.Configure<SecurityStampValidatorOptions>(options =>
{
// set time how oftern the cookie is invalidated
options.ValidationInterval = TimeSpan.FromMinutes(10);
options.OnRefreshingPrincipal = context =>
{
// Get my custom claim from old cookie
var guidClaim = context.CurrentPrincipal.FindFirst("GUID");
// Add value to new cookie
if (guidClaim != null)
context.NewPrincipal.Identities.First().AddClaim(guidClaim);
return Task.FromResult(0);
};
});

Resources