Azure authentication .net MVC timeout settings - asp.net-mvc

having searched for the answer to this I cant find any way of extending the timeout for a logged in user. legacy .net framework project 4.7, ASP.NET mvc with Microsoft.OWIN packages.
The login process appears to work ok.
After an hour I get redirected to the microsoft login page even when using the site. sliding expiration does nothing.
What settings or techniques do I change to make the session last longer?
The expected behaviour is that the session would be kept alive by using the site by navigating or even having a modal prompt to continue the session would work without losing anything that is in progress by being redirected.
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieManager = new SystemWebCookieManager(),
SlidingExpiration = true
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = AuthenticationConfig.Authority,
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = postLogoutRedirectUri,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = tenant,
NameClaimType = "name",
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
SecurityTokenValidated = OnSecurityTokenValidated,
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived
}
});
}

In startup class try to use
Session.Timeout = <value in minutes>;
UseTokenLifetime = false, //do this for ExpireTimeSpan to be respected
ExpireTimeSpan = TimeSpan.FromMinutes(30);
ex:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
...
UseTokenLifetime = false,
...
});
If SlidingExpiration is set to true . For example, if the user logged in and made a second request 19 minutes later the cookie will be re-issued for another 30 minutes.
Try changing session timeout value in web.config. For example, code in web.config under<system.web> namespace.
<sessionState timeout="<value in minutes>" />
In some cases,even when session timeout is increased, session will still expire.
some possible reasons might be.
session timeout should be less than Application pool idle timeout, so if you increase session timeout, you have to increase application idle timeout too. Otherwise, application will get recycled. Hence sessions will expire automatically.
note that if you use Forms Authentication, you'll need to increase forms timeout too in web.config :
<system.web>
...
<authentication mode="Forms">
<forms timeout="60"/>
</authentication>
...
</system.web>
Also please check Configure token lifetime if needed
References:
configuring-authentication-session-controls
Session-Timeout-Expiration

Related

Transient Infinite Login Loop on Azure App Service - OpenIDConnect Auth with Azure AD

Background
So we have an app service that authenticates from Azure AD in another tenancy using OpenIdConnect.
Login works on a dev instance of IIS, and it works on our test app service. We saw the issue on test, and it vanished and didn't return during the entire testing phase of the project.
Now we've deployed to production, and we're seeing the issue again.
The Issue
What we're seeing is that everything will work fine for some time, and then after several hours, the issue will emerge again.
We have a work around fix to restore service - that is to enable and then disable app service authentication in the azure control panel. The opposite works too - to disable and then enable will restore service.
The Code
public void ConfigureAuth(IAppBuilder app)
{
//Azure AD Configuration
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
//sets client ID, authority, and RedirectUri as obtained from web config
ClientId = clientId,
ClientSecret = appKey,
Authority = authority,
RedirectUri = redirectUrl,
CallbackPath = new PathString("/"), //use this line for production and test
//page that users are redirected to on logout
PostLogoutRedirectUri = redirectUrl,
//scope - the claims that the app will make
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
//setup multi-tennant support here, or set ValidateIssuer = true to config for single tennancy
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
//SaveSigninToken = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
}
}
);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential cred = new ClientCredential(clientId, appKey);
string userObjectId = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
//this token cache is stateful, we're going to populate it here, but we'll store it elsewhere in-case the user ends up accessing a different instance
AuthenticationContext authContext = new AuthenticationContext(authority, new NaiveSessionCache(userObjectId));
// If you create the redirectUri this way, it will contain a trailing slash.
// Make sure you've registered the same exact Uri in the Azure Portal (including the slash).
Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, cred, "https://graph.windows.net");
//populate the persistent token cache
testdb2Entities5 db = new testdb2Entities5();
PersistentTokenCache tc = await db.PersistentTokenCaches.FindAsync(userObjectId);
//if null, populate a new item
if (tc == null)
{
tc = new PersistentTokenCache();
tc.object_id = userObjectId;
tc.token = code;
db.PersistentTokenCaches.Add(tc);
await db.SaveChangesAsync();
}
else
{
tc.token = code;
await db.SaveChangesAsync();
}
}
//authentication failed notifications
private Task OnAuthenticationFailed(AuthenticationFailedNotification<Microsoft.IdentityModel.Protocols
.OpenIdConnect.OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
The Question
So, based on whatever enabling and disabling app-service authentication does, it's clearly fixing thing temporarily. So I'm thinking this is a cookie related problem - as that'd be the only thing transferring state between sessions. What on earth could be the issue here? And what steps do I need to take to diagnose and resolve the issue?
So far, it seems like it was a problem with a known bug in Katana where the Katana cookie manager and the ASP .NET cookie manager clash and overwrite each other's cookies.
Here are some troubleshoots you could refer to:
1.Setting
app.UseCookieAuthentication(new CookieAuthenticationOptions {CookieSecure == CookieSecureOption.Always}). This means that cookie could leak along with your auth.
2.Add SystemWebCookieManager in UseCookieAuthentication which is in the Microsoft.Owin.Host.SystemWeb Nuget package. Please refer to this thread.
3.Split cookie. Someone noticed that it was issue if Cookie characters are more than browsers limit (> 4096). So to overcome that issue, in set-cookie with around 4000 characters with each and when needed combine all cookie together to get original value.
For more details about how to add sign-in with Microsoft to an ASP.NET web app, please refer to this article.
Update:
Fix with install Kentor.OwinCookieSaver nuget package and add app.UseKentorOwinCookieSaver(); before app.UseCookieAuthentication(new CookieAuthenticationOptions());

How to enable sliding expiration in IdentityServer

I am using IdentityServer3 for authentication and I have ASP.NET MVC application as Client. I want to setup sliding expiration of authentication cookie.
So as long as user is actively doing something in client application he should be logged in. If he remains inactive (with browser open) for more than 120 mins and then try to use client application then he should get redirected to log in page.
There are bunch of settings related to sliding expiration In IdentityServer3's IdentityServerOptions and also in client application's CookieAuthenticationOptions and OpenIdConnectAuthenticationOptions
On Identity Server i have the following configuration
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Login",
SigningCertificate = LoadCertificate(),
RequireSsl = true,
Factory = new IdentityServerServiceFactory()
.Configure(),
AuthenticationOptions = new AuthenticationOptions()
{
CookieOptions = new CookieOptions()
{
AllowRememberMe = false,
SlidingExpiration = true
}
}
.Configure(ConfigureIdentityProviders),
EventsOptions = new EventsOptions().Configure(),
EnableWelcomePage = ApplicationConfig.EnableWelcomePage
});
});
}
I have set the Client.IdentityTokenLifetime to 7200 seconds
In client application i have the following configuration
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
LoginPath = new Microsoft.Owin.PathString("/Home"),
SlidingExpiration = true
};
var openIdOptions = new OpenIdConnectAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["id:Authority"],
Scope = "openid email profile",
ClientId = "XXXXXXXXX",
RedirectUri = "http://localhost/Home",
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = true,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = (context) =>
{
// do something
},
RedirectToIdentityProvider = (context) =>
{
// do something
},
AuthenticationFailed = context =>
{
// do something
}
}
};
app.UseCookieAuthentication(cookieOptions);
app.UseOpenIdConnectAuthentication(openIdOptions);
Note that i have set UseTokenLifetime to true so the cookie timeout will aligned with Client.IdentityTokenLifetime
ISSUE
Even if the user is active for 120 mins, He gets logged out exactly after 120 mins.
What else i need to do enable sliding expiration?
(I have already gone through several post on SO and also IdentityServer's forum but no one has concrete answer)
#thunk got me kind of on the right track here, and his answer is essentially what solved this issue for me and let me know what to search for to get an understanding. I just wanted to "add to it" in hopes that it will help someone else out.
I spent a considerable amount of time trying to figure this out, and it was compounded by lack of explanation in the samples and documentation. If you go through the MVC Getting Started for IdentityServer3, they sneak this UseTokenLifetime setting in on you (halfway through the example code) without mentioning that they added it or what its for. At first, they use this code (in the Startup.cs of your MVC app):
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44319/identity",
ClientId = "mvc",
RedirectUri = "https://localhost:44319/",
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies"
});
Then later on they sneak in UseTokenLifetime:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44319/identity",
ClientId = "mvc",
Scope = "openid profile roles",
RedirectUri = "https://localhost:44319/",
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
// other stuff continues...
});
As I was going through the tutorial and typing in my own code as I went, I missed the UseTokenLifetime = false being snuck in, and they didn't mention that it was done, or WHY it was done.
Here is a nice bit of info I found that confirms I'm not the only one with this confusion, and explains what is going on a little better
For posterity, what I found most confusing about this was that I can set a cookie lifetime in my cookie options:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
ExpireTimeSpan = new TimeSpan(4, 0, 0),
SlidingExpiration = true,
});
But if I don't know to override the OIDC defaults, that ExpireTimeSpan is ignored/overwritten.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
...
UseTokenLifetime = false, // have to know to do this for ExpireTimeSpan to be respected
...
});
That behavior seems incredibly opaque to me. Not sure if it could be helped by different naming, or what, but it seems like it will be a common misunderstanding in practice, though I don't profess to understand the majority of use cases.
As a side note, the MSDN on the UseTokeLifetime property (the only place I can even find any documentation on this property!!!) is horrible:
Indicates that the authentication session lifetime (e.g. cookies) should match that of the authentication token. If the token does not provide lifetime information then normal session lifetimes will be used. This is enabled by default.
Not sure why they just don't come out and say that token lifetime information WILL overwrite the normal session times.
And all of that comes full circle back to what I still don't understand... From what I've read, the cookie lifetimes don't have anything to do with the validity or expiration of the authentication ticket that is inside the cookie. In other words, you can't just look at the expiration time on the cookie to know when your authentication expires. And I failed to understand that the settings in the CookieAuthenticationOptions don't actually control the cookie expiration time, they control the embedded authentication ticket expire time. From this blog post:
.AddCookie(options =>
{
// Configure the client application to use sliding sessions
options.SlidingExpiration = true;
// Expire the session of 15 minutes of inactivity
options.ExpireTimeSpan = TimeSpan.FromMinutes(15);
})
When I first configured this, I wrongly assumed that this would set the expiration of the cookie itself, as reflected in the browsers development tools. However, this is in fact a setting of the ticket that is stored inside the cookie, not of the cookie itself. It is this ticket that is evaluated by the MVC client whenever a request is handled. This ticket determines the validity of the users authentication session.
TL;DR
If UseTokenLifetime is not set or set to true, then your authentication ticket will be valid as long as your id_token is valid for (default 5 minutes).
If UseTokenLifetime is set to false, then your CookieAuthenticationOptions settings take over, namely ExpireTimeSpan and SlidingExpiration.
Anyway, hope this answer helps someone else gain enlightenment.
SlidingExpiration is on your Cookie middleware only. As you are using the Token Lifetime, any setting you have here is overriden/ignored.
For it to become active UseTokenLifetime must be set to false. My experience so far is that UseTokenLifetime = false; must be set on both your openIdOptions on client and within the CookieOptions on IdentityServer.

CookieAuthenticationOptions.LoginPath value not used when also using app.UseOpenIdConnectAuthentication

I am using OWIN middleware for cookie authentication and openIdConnect. Before I added openIdConnect authentication to my startup auth code the cookie authentication option, LoginPath was used as the destination for redirecting unauthenticated users. This worked really well and is the functionality I would like to keep.
However, when I added app.UseOpenIdConnectAuthentication to my project, it started automatically redirecting unauthenticated users to my OpenIdConnect Authority (https://login.windows.net/).
Is there a way I can disable OpenIdConnectAuthentication setting the redirect path for unauthenticated users and rely on the LoginPath set for cookie authentication? My current work around is to manually set the redirect path in my authorize attribute, but I would like to let OWIN middleware handle this if possible.
Thanks.
Code:
public void ConfigureAuth(IAppBuilder app)
{
// 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
var cookieOptions = new CookieAuthenticationOptions();
cookieOptions.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie;
cookieOptions.LoginPath = new PathString("/Account/Login");
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType);
app.UseCookieAuthentication(cookieOptions);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = FranchiseAuthType,
ClientId = franchiseClientId,
Authority = FranchiseAuthority,
PostLogoutRedirectUri = postLogoutRedirectUri,
});
}
I'm not sure if you were able to resolve this issue, but what you want to do it is add
AuthenticationMode = AuthenticationMode.Passive
To your authentication options. This will make the OpenIdConnect authentication rely solely on your code to make calls to it. I believe this is what you intend to happen.
So your new code should look like this:
public void ConfigureAuth(IAppBuilder app)
{
// 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
var cookieOptions = new CookieAuthenticationOptions();
cookieOptions.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie;
cookieOptions.LoginPath = new PathString("/Account/Login");
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType);
app.UseCookieAuthentication(cookieOptions);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = FranchiseAuthType,
AuthenticationMode = AuthenticationMode.Passive,
ClientId = franchiseClientId,
Authority = FranchiseAuthority,
PostLogoutRedirectUri = postLogoutRedirectUri,
});
}
EDIT: This looked like it fixed my problems but it caused a more serious issue than the one it fixed. If I set use cookies after use open Id connect then my openidconnect notifications will have a null sessions HttpContext.Current.Session and after authentication my authentication result is not stored in the cookie.
Whichever authentication is added last becomes the authoritative source for setting the redirect path for unauthenticated uses.
By moving
app.UseCookieAuthentication();
After app.UseOpenIdConnectAuthentication() the desired behavior was achieved.
public void ConfigureAuth(IAppBuilder app)
{
// 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
var cookieOptions = new CookieAuthenticationOptions();
cookieOptions.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie;
cookieOptions.LoginPath = new PathString("/Account/Login");
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = FranchiseAuthType,
ClientId = franchiseClientId,
Authority = FranchiseAuthority,
PostLogoutRedirectUri = postLogoutRedirectUri,
});
//move this after UseOpenIdConnectAuthentication and the LoginPath
//value is used for redirecting unauthenticated users
app.UseCookieAuthentication(cookieOptions);
}

Why does Owin Startup order affect Cookie Authentication

I have Owin configured to issue both a token and cookie upon authentication:
public void Configuration(IAppBuilder app)
{
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true, // JavaScript should use the Bearer
//AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieName = "MyCookie",
LoginPath = new PathString("/app/index.html#/login"),
};
var oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new MyAuthorizationServerProvider(),
};
var oAuthBearerOptions = new OAuthBearerAuthenticationOptions
{
};
// Must be registered in this order!
app.UseCookieAuthentication(cookieOptions);
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(oAuthBearerOptions);
}
This works fine - it issues both the Bearer token for my SPA to call my API and the cookie so my old school MVC pages can be logged in too.
But if I register the OAuth server before declaring that I want to use CookieAuth, no cookie is issued. In other words if I do this, it doesn't work:
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseCookieAuthentication(cookieOptions);
app.UseOAuthBearerAuthentication(oAuthBearerOptions);
Also, if I uncomment this line, it also doesn't issue the cookie:
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
My question is why does the order of registration with Owin matter? And why does setting the AuthenticationType in the cookie as "ApplicationCookie" also make it fail?
I'm not familiar with the UseOAuthAuthorizationServer() middleware, but I assume it works the same as other external authentication middleware (e.g. the Google middleware).
An authentication middleware that redirects to an external source for authentication, will only be used once, at the start, of each browsing session. It will then defer the authentication of the up-coming requests to a cookie authentication that maintains the session. This is good, because it means the overhead of the external authentication is only done once for each session.
A middleware that wants to set a cookie does typically not do it itself. Instead it sets a property in the Owin context with an AuthenticationResponseGrant. The grant is then processed by the cookie middleware that extracts the identity and sets the cookie.
For this to work:
The cookie handler must be registered before the external authentication middleware in the pipeline.
The authentication type in the AuthenticationResponseGrant must match the type of the cookie middleware.
So changing the order of the registration violates 1. and excluding the authentication type violates 2.
I have written an in depth blog post about it if you want more details.

Using cookies to stay signed in with third party login providers and Microsoft.AspNet.Identity.Owin 2.0

I've followed this tutorial in an attempt to use several third party login providers with a simple ASP.NET MVC SPA application I am writing. While configuration is simple enough (I've actually enabled Twitter and Microsoft), and the sign-in process works correctly, the user credentials are stored in a browser session cookie only and do not persist across browser sessions.
I've also tried using the alpha-1 sample project from NuGet (with the same basic configuration applied) and it also does not work (at least in my environment).
The web application is only hosted locally (as I do not have an Azure account in which to test).
I thought the setting ExpireTimeSpan would affect it, but it does not:
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(GetCookieAuthenticationOptions());
private static CookieAuthenticationOptions GetCookieAuthenticationOptions()
{
var options = new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieSecure = CookieSecureOption.SameAsRequest,
SlidingExpiration = true,
CookieName = "MYSECURITY",
ExpireTimeSpan = TimeSpan.FromDays(45.0),
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(20),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
};
return options;
}
Cookies (I changed the default name of the cookie intentionally to validate that the code was executing -- it doesn't work with the default either):
The MVC Single Page Application project template in Visual Studio contains the following method in the AccountController which forces all all external logins to not be persistent across browser sessions:
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
// ...
await SignInAsync(user, isPersistent: false);
// ...
}
If you are comfortable with the security implications of trusting an identity that has been authenticated by an external provider across browser sessions, you could set isPersistent = true when calling SignInAsync.
Also be aware that any persistent login will be made non-persistent once the SecurityStampValidator fires the regenerateIdentity callback (which will occur after 20 minutes in your sample code above). See the question ExpireTimeSpan ignored after regenerateIdentity / validateInterval duration in MVC Identity (2.0.1) for discussion on this behavior.

Resources