ASP.NET MVC 5 OWIN Cookie Expiration - asp.net

I am using OWIN middleware in my web app to manage logins/claims and issuing a cookie. The initial requirement was that the user should automatically be logged out when the browser was closed. That was easy enough, and accomplished by setting AuthenticationProperties.IsPersistent = false.
A new requirement came in that not only should the user be automatically logged out upon browser close, but they should also timeout after one hour of inactivity. I updated my ConfigureAuth method within my Startup class to provide an ExpireTimeSpan and set SlidingExpiration = true. To make those changes work, I then set AuthenticationProperties.IsPersistent = true.
Those changes made the user timeout after an hour, but now, since the cookie is persistent, the user is no longer automatically logged out upon browser close. Is there anyway to get both of these functionalities? I want my cake and eat it too!
Code used for reference:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
ExpireTimeSpan = TimeSpan.FromHours(1),
SlidingExpiration = true,
LoginPath = new PathString("/Consent/Index"),
ReturnUrlParameter = "returnUrl"
});
}
}
and
private void UpdateClaims(User user)
{
var claims = new List<Claim>();
claims.Add(new Claim(BlahBlahBlah));
claims.Add(new Claim(MoreBlahBlah));
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.Name, ClaimTypes.Role);
Authentication.SignIn(new AuthenticationProperties
{
IsPersistent = true
}, identity);
}

Related

User unauthorized after Azure AD login to different application simultaneously

I have two MVC applications AppA and AppB, and implemented Azure AD authentication for login.
I am able to sign-in successfully to both applications.
But the issue is, after I login to AppA and then to AppB, after sometime when I return back to AppA I am facing the issue where user has been logged out, and it again redirects to login screen (in AppA).
After I login to AppA (second time) and go back to AppB (user in AppB is logged out).
Client IDs are different ; TenandID is same. Both apps are hosted in same server.
Startup file:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
OnResponseSignIn = context =>
{
context.Properties.AllowRefresh = true;
context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddDays(1);
},
OnValidateIdentity = MyCookieValidateIdentity
},
ExpireTimeSpan = TimeSpan.FromDays(2)
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
//CookieManager=new SameSiteCookieManager(new SystemWebCookieManager()),
Authority = "https://login.microsoftonline.com/xxxxxx/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.DomainHint = "xyz.com";
return Task.FromResult(0);
},
// SecurityTokenValidated = OnSecurityTokenValidated,
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
}
actionContext.RequestContext.Principal.Identity.IsAuthenticated is returning False
I am assuming it has to do something with the cookie. Can someone please help resolve this ?
Edit:
Debugged further and found:
Initially if the cookies for AppA are set as:
.AspNet.Cookies = A_abc123 ; ASP.NET_SessionId = A_def456
And for AppB .AspNet.Cookies = B_mno123 ; ASP.NET_SessionId = B_pqr456
Then after I click any link in AppA, the cookie's values are updated with AppB's cookies, i.e. .AspNet.Cookies = B_mno123 ; ASP.NET_SessionId = B_pqr456
.AspNet.Cookies ASP.NET_SessionId
AppA A_abc123 A_def456
AppB B_mno123 B_pqr456
AppA B_mno123 B_pqr456
One thing that you need to do is to configure the Data Protection API so that both services uses the same cookie protection key. Out of the box each service creates its own unique key, and a cookie from one service is not valid in a different service.
I also did a blog post about the data protection API here.
See
How to: Use Data Protection
Get started with the Data Protection APIs in ASP.NET Core
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
//AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,// DefaultAuthenticationTypes.ApplicationCookie,
CookieName = ".AspNet.AppA.Cookies",
SlidingExpiration = true,
CookieManager = new SystemWebCookieManager(),
Provider = new CookieAuthenticationProvider
{
OnResponseSignIn = context =>
{
context.Properties.AllowRefresh = true;
context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddDays(1);
},
},
ExpireTimeSpan = TimeSpan.FromDays(2)
});
//... code removed for brevity //
}
The Default Cookie Name set by the application was: .AspNet.Cookies
And when I modified the default cookie name, the issue got resolved. Each application was generating its own cookiename and hence the other application was not signing out the user.

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.

Asp.Net Identity with 2FA - remember browser cookie not retained after session

I'm using the latest sample code for MVC5.2 with Asp.Identity and Two Factor authentication.
With 2FA enabled, when a user logins, the get prompted for a code (sent by phone or email) and they have the option to "Remember Browser" - so that they don't get ask for codes again on that browser.
This is handled in the VerifyCode action
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
Note that model.RememberMe is not used in the default templates so it is false.
I find when I do this the .AspNet.TwoFactorRememberBrowser that gets set, expires on session end (so it does not remember the browser)
Now if I set isPersistent = true, .AspNet.TwoFactorRememberBrowser gets an expiration of 30 days which is great, but the .AspNet.ApplicationCookie also gets a 30 day expiration - which means that when I close the browser and re-open, I am automatically logged in.
I want it so that it doesn't persist my login, but that it will persist my choice of remembering the 2FA code. Ie the user should always have to login, but they should not be asked for a 2fa code if they have already save it.
Has anybody else seen this, or am I missing something?
It doesn't seem like this code was designed to set more than one identity cookie in the same request/response because the OWIN cookie handlers end up sharing the same AuthenticationProperties. This is because the AuthenticationResponseGrant has a single principal, but the principal can have multiple identities.
You can workaround this bug by altering and then restoring the AuthenticationProperties in the ResponseSignIn and ResponseSignedIn events specific to the 2FA cookie provider:
//Don't use this.
//app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
//Set the 2FA cookie expiration and persistence directly
//ExpireTimeSpan and SlidingExpiration should match the Asp.Net Identity cookie setting
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
AuthenticationMode = AuthenticationMode.Passive,
CookieName = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
ExpireTimeSpan = TimeSpan.FromHours(2),
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
OnResponseSignIn = ctx =>
{
ctx.OwinContext.Set("auth-prop-expires", ctx.Properties.ExpiresUtc);
ctx.OwinContext.Set("auth-prop-persist", ctx.Properties.IsPersistent);
var issued = ctx.Properties.IssuedUtc ?? DateTimeOffset.UtcNow;
ctx.Properties.ExpiresUtc = issued.AddDays(14);
ctx.Properties.IsPersistent = true;
},
OnResponseSignedIn = ctx =>
{
ctx.Properties.ExpiresUtc = ctx.OwinContext.Get<DateTimeOffset?>("auth-prop-expires");
ctx.Properties.IsPersistent = ctx.OwinContext.Get<bool>("auth-prop-persist");
}
}
});
Make sure to set the same ExpireTimeSpan and SldingExpiration as your main Asp.Net Identity cookie to preserve those settings (since they get merged in the AuthenticationResponseGrant).
This still appears to be an issue in Identity 2.2.1 (It may be fixed in Asp.Net Identity 3.0 - but that is currently pre-released and requires a later version of .Net framework that 4.5)
The following work around seems ok for now:
The cookie is getting set on the SignInManager.TwoFactorSignInAsync with the wrong values, so on Success of the VerifyCode action, I reset the cookie to be persistent and give it the expiry date that I wish (in this case I set it to a year)
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
} var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
switch (result)
{
case SignInStatus.Success:
// if we remember the browser, we need to adjsut the expiry date as persisted above
// Also set the expiry date for the .AspNet.ApplicationCookie
if (model.RememberBrowser)
{
var user = await UserManager.FindByIdAsync(await SignInManager.GetVerifiedUserIdAsync());
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(user.Id);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTime.UtcNow.AddDays(365) }, rememberBrowserIdentity);
}
return RedirectToLocal(model.ReturnUrl);
What you can do is assign your own CookieManager class that modifies the expiration time of the TwoFactorRememberBrowserCookie. This seems better than modifing the cookie in Application_PostAuthenticateRequest.
This works around the problem that you can either persist all or none of the authentication cookies.
Put this in your ConfigureAuth, the last line sets your custom cookie manager.
public void ConfigureAuth(IAppBuilder app)
{
// left out all but the modified initialization of the TwoFactorRememberBrowserCookie
var CookiePrefix = ".AspNet.";
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
CookieName = CookiePrefix + DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
ExpireTimeSpan = TimeSpan.FromDays(14),
CookieManager = new TwoFactorRememberBrowserCookieManager()
});
}
Use this CookieManager class only for the TwoFactorRememberBrowserCookie.
When you do not persist cookies in TwoFactorSignInAsync, unfortunately the ExpirationTimeout is ignored.
So just set it again in the CookieManager (This is a modified version of the cookie manager coming from Microsoft.Owin.Infrastructure.CookieManager):
public class TwoFactorRememberBrowserCookieManager : Microsoft.Owin.Infrastructure.ICookieManager
{
string CookiePrefix = ".AspNet.";
Microsoft.Owin.Infrastructure.ICookieManager cm = new Microsoft.Owin.Infrastructure.ChunkingCookieManager();
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
if (key == CookiePrefix + DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie) {
options.Expires = DateTime.UtcNow.AddDays(14);
}
cm.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
cm.DeleteCookie(context, key, options);
}
public string GetRequestCookie(IOwinContext context, string key)
{
return cm.GetRequestCookie(context, key);
}
}
This is what you will get:
Works for me that way.

If Authorize fail in Web-api (asp.net) , browser show basic authentication

I just want to get Error Message.
But application show It needs basic login.
How can I pass it?
AccountServiceController
[Authorize]
[Route("UserRoles")]
public async Task<IHttpActionResult> GetUserRoles(string userName){
//my code
}
Strat.Auth
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
ExpireTimeSpan = System.TimeSpan.FromDays(1)
});
var OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
Provider = new DrDB.Identity.Infrastructure.SimpleAuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
You're going about this the wrong way.
The WebAPI shouldn't be displaying the logon prompt - the calling application should.
If you check the response that the WebAPI is returning, it's likely a 401 (unauthorized).
The caller can check for a 401 response, and display whatever login page it needs to.

How do I ignore the Identity Framework magic and just use the OWIN auth middleware to get the claims I seek?

The OWIN middleware stuff to integrate third-party logins to your ASP.NET app is very cool, but I can't seem to figure out how to tear it out from the new ID framework that replaces the crappy Membership API. I'm not interested in persisting the resulting claims and user info in that EF-based data persistence, I just want the claims info so I can apply it to my own user accounts in existing projects. I don't want to adopt the new ID framework just to take advantage of this stuff.
I've been browsing the code on CodePlex, but there's a whole lot of static magic. Can you offer any suggestions?
Use the following code to setup OWIN security middlewares:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Application",
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Login"),
LogoutPath = new PathString("/Logout"),
});
app.SetDefaultSignInAsAuthenticationType("External");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "External",
AuthenticationMode = AuthenticationMode.Passive,
CookieName = CookieAuthenticationDefaults.CookiePrefix + "External",
ExpireTimeSpan = TimeSpan.FromMinutes(5),
});
app.UseGoogleAuthentication();
The code above sets up application cookie, external cookie and Google external login middlewares. External login middleware will convert external user login data as identity and set it to external cookie middleware. In your app, you need to get external cookie identity and convert it to external login data, then you can check it with your db user.
Here are some sample code.
Sign in with application cookie:
var authentication = System.Web.HttpContext.Current.GetOwinContext().Authentication;
var identity = new ClaimsIdentity("Application");
identity.AddClaim(new Claim(ClaimTypes.Name, "<user name>"));
authentication.AuthenticationResponseGrant = new AuthenticationResponseGrant(identity, new AuthenticationProperties() {
IsPersistent = false
});
Get application cookie identity:
var identity = System.Web.HttpContext.Current.User.Identity as ClaimsIdentity;
Get external cookie identity (Google):
var authentication = System.Web.HttpContext.Current.GetOwinContext().Authentication;
var result = await authentication.AuthenticateAsync("External");
var externalIdentity = result.Identity;
Extract external login data from identity:
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
if (identity == null)
{
return null;
}
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
|| String.IsNullOrEmpty(providerKeyClaim.Value))
{
return null;
}
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
{
return null;
}
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name)
};
}

Resources