c# - ASP.NET Identity regenerats Identity at each request -
i have asp.net mvc application , i'm using asp.net identity 2. have weird problem. applicationuser.generateuseridentityasync getting called each request browser makes website. i've added trace.writeline , result after removing iis output:
identityconfig.configuration called applicationuser.generateuseridentityasync called url: http://localhost:54294/ applicationuser.generateuseridentityasync called url: http://localhost:54294/content/bootstrap.css applicationuser.generateuseridentityasync called url: http://localhost:54294/scripts/modernizr-2.8.3.js applicationuser.generateuseridentityasync called url: http://localhost:54294/content/site.css applicationuser.generateuseridentityasync called url: http://localhost:54294/scripts/jquery-2.1.3.js applicationuser.generateuseridentityasync called url: http://localhost:54294/scripts/bootstrap.js applicationuser.generateuseridentityasync called url: http://localhost:54294/scripts/respond.js applicationuser.generateuseridentityasync called url: http://localhost:54294/scripts/script.js applicationuser.generateuseridentityasync called url: http://localhost:54294/glimpse.axd?n=glimpse_client&hash=8913cd7e applicationuser.generateuseridentityasync called url: http://localhost:54294/glimpse.axd?n=glimpse_metadata&hash=8913cd7e&callback=glimpse.data.initmetadata applicationuser.generateuseridentityasync called url: http://localhost:54294/glimpse.axd?n=glimpse_request&requestid=6171c2b0-b6e5-4495-b495-4fdaddbe6e8f&hash=8913cd7e&callback=glimpse.data.initdata applicationuser.generateuseridentityasync called url: http://localhost:54294/glimpse.axd?n=glimpse_sprite&hash=8913cd7e applicationuser.generateuseridentityasync called url: http://localhost:54294/__browserlink/requestdata/38254292a54f4595ad26158540adbb6a?version=2 while if run default mvc application created template, i'm getting this:
identityconfig.configuration called and if login, it'll call applicationuser.generateuseridentityasync.
i've looked everywhere thought might didn't find result. i'm using (if helps)
structuremap 3 elmah glimpse asp.net mvc 5 ef6 asp.net identity 2 additional info
i'm adding users directly database without using usermanage. i'm not sure if makes problems identity or not.
update
i've dropped database , didn't happen anymore. happening?
update 2
it happened in google chrome (i monitor sql connections using glimpse) , after removing stored cookies, didn't happen. can logging in in other browsers cause problem?
update 3
also log off - log in seems solve problem temporary.
i had same problem , after digging in source code , detective work found solution. problem inside securitystampvalidator, used default onvalidateidentity handler. see source code here. interesting part:
var issuedutc = context.properties.issuedutc; // validate if enough time has elapsed var validate = (issuedutc == null); if (issuedutc != null) { var timeelapsed = currentutc.subtract(issuedutc.value); validate = timeelapsed > validateinterval; } this part runs each request , if validate true, getuseridcallback , regenerateidentitycallback (visible in trace output) called. problem here issuedutc date when cookie created, validate true when validateinterval has passed. explains weird behavior experiencing. if validateinterval 10 minutes, validation logic run each request coming in 10 minutes , more after cookie created (application deployed, cookies cleared, cookie reset when logging out , in again).
securitystampvalidator should make decisions whether validate or not, basing on previous validation date (or issued date when it's first check), it's not doing so. make issuedutc date move forward there 3 possible solutions:
- reset cookie each
validateinterval, meanssingout,signin. similar solution here. seems costly operation, ifvalidateintervalset couple of minutes. - take advantage of
cookieauthenticationoptions.slidingexpirationlogic have cookie re-issued automatically. explained in this post.
if slidingexpiration set true cookie re-issued on request half way through expiretimespan. example, if user logged in , made second request 16 minutes later cookie re-issued 30 minutes. if user logged in , made second request 31 minutes later user prompted log in.
in case (intranet application) users being logged out after 30 minutes of inactivity unacceptable. need have default expiretimespan, 14 days. option here implement kind of ajax polling extend cookie life. sounds lot of effort accomplish simple scenario.
the last option, chose use modify
securitystampvalidatorimplementation have sliding validation approach. example code below. remember replacesecuritystampvalidatorslidingsecuritystampvalidatorin startup.auth.cs. addedidentityvalidationdatesdictionary original implementation store validation dates each user , i'm using when checking if validation needed.public static class slidingsecuritystampvalidator { private static readonly idictionary<string, datetimeoffset> identityvalidationdates = new dictionary<string, datetimeoffset>(); public static func<cookievalidateidentitycontext, task> onvalidateidentity<tmanager, tuser, tkey>( timespan validateinterval, func<tmanager, tuser, task<claimsidentity>> regenerateidentitycallback, func<claimsidentity, tkey> getuseridcallback) tmanager : usermanager<tuser, tkey> tuser : class, iuser<tkey> tkey : iequatable<tkey> { if (getuseridcallback == null) { throw new argumentnullexception(nameof(getuseridcallback)); } return async context => { var currentutc = datetimeoffset.utcnow; if (context.options != null && context.options.systemclock != null) { currentutc = context.options.systemclock.utcnow; } var issuedutc = context.properties.issuedutc; // validate if enough time has elapsed var validate = issuedutc == null; if (issuedutc != null) { datetimeoffset lastvalidateutc; if (identityvalidationdates.trygetvalue(context.identity.name, out lastvalidateutc)) { issuedutc = lastvalidateutc; } var timeelapsed = currentutc.subtract(issuedutc.value); validate = timeelapsed > validateinterval; } if (validate) { identityvalidationdates[context.identity.name] = currentutc; var manager = context.owincontext.getusermanager<tmanager>(); var userid = getuseridcallback(context.identity); if (manager != null && userid != null) { var user = await manager.findbyidasync(userid); var reject = true; // refresh identity if stamp matches, otherwise reject if (user != null && manager.supportsusersecuritystamp) { var securitystamp = context.identity.findfirstvalue(constants.defaultsecuritystampclaimtype); if (securitystamp == await manager.getsecuritystampasync(userid)) { reject = false; // regenerate fresh claims if possible , resign in if (regenerateidentitycallback != null) { var identity = await regenerateidentitycallback.invoke(manager, user); if (identity != null) { // fix regression value not updated // setting null refreshed cookie middleware context.properties.issuedutc = null; context.properties.expiresutc = null; context.owincontext.authentication.signin(context.properties, identity); } } } } if (reject) { context.rejectidentity(); context.owincontext.authentication.signout(context.options.authenticationtype); } } } }; } }
Comments
Post a Comment