RobMinnis
asked on
ASP.NET Forms Authentication - Subsequent logins redirected to wrong page
I have an ASP.NET 3.5 application that uses Forms Authentication. When first launching a browser, everything works correctly (login, access protected pages, logout).
After SignOut(), no user can use the app without closing the browser (tested IE and Safari). Specifically, after the next user logs in, the response is redirected to the app's public home page and not to the private page.
- I've verified that the auth cookie is no longer present.
- The code path is the same in both cases
- I do use a Login control but I manually control the login process (auth, ticket, and redirect)
- At login, the Session ID is changed
- This behavior exists regardless of whether I am using a developer server or a production server
Everything appears to be correct but the user is always redirected to the app's home page. (I have a default.aspx file that is not used; it performs a redirect on load but this code is never called)
I use the FormsAuthentication SignOut() routine, I Abandon() the session, I've verified that my cookies and the auth cookie are no longer present, and I've checked the Response redirect URL - What else controls the final page after login? And, since the session is new, what else does the server manage that could interfere with authenticating a second user?
After SignOut(), no user can use the app without closing the browser (tested IE and Safari). Specifically, after the next user logs in, the response is redirected to the app's public home page and not to the private page.
- I've verified that the auth cookie is no longer present.
- The code path is the same in both cases
- I do use a Login control but I manually control the login process (auth, ticket, and redirect)
- At login, the Session ID is changed
- This behavior exists regardless of whether I am using a developer server or a production server
Everything appears to be correct but the user is always redirected to the app's home page. (I have a default.aspx file that is not used; it performs a redirect on load but this code is never called)
I use the FormsAuthentication SignOut() routine, I Abandon() the session, I've verified that my cookies and the auth cookie are no longer present, and I've checked the Response redirect URL - What else controls the final page after login? And, since the session is new, what else does the server manage that could interfere with authenticating a second user?
ASKER
The DefaultURL is the same in both cases and I override the default with an explicit redirect. As mentioned, the default.aspx file is present but is not accessed.
Can you post your web.config, specifically the <forms section, and the code that handles your Login?
ASKER
Login -------------------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
System.Web.Security.FormsA uthenticat ion.SetAut hCookie(na me, li_Login.RememberMeSet);
Response.Redirect("~/Gener al/Home.as px", false);
Logout -------------------------- ---------- ---------- ---------- ---------- ---------- ---------- --
Session.Abandon();
Session.Clear();
Session.Contents.RemoveAll ();
System.Web.Security.FormsA uthenticat ion.SignOu t();
Response.Redirect("~/Publi c/Main.asp x"), true);
Web Config -------------------------- ---------- ---------- ---------- ---------- ---------- --
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.extension s" type="System.Web.Configura tion.Syste mWebExtens ionsSectio nGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35">
<sectionGroup name="scripting" type="System.Web.Configura tion.Scrip tingSectio nGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35">
<section name="scriptResourceHandle r" type="System.Web.Configura tion.Scrip tingScript ResourceHa ndlerSecti on, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35" requirePermission="false" allowDefinition="MachineTo Applicatio n"/>
<sectionGroup name="webServices" type="System.Web.Configura tion.Scrip tingWebSer vicesSecti onGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35">
<section name="jsonSerialization" type="System.Web.Configura tion.Scrip tingJsonSe rializatio nSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35" requirePermission="false" allowDefinition="Everywher e"/>
<section name="profileService" type="System.Web.Configura tion.Scrip tingProfil eServiceSe ction, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35" requirePermission="false" allowDefinition="MachineTo Applicatio n"/>
<section name="authenticationServic e" type="System.Web.Configura tion.Scrip tingAuthen ticationSe rviceSecti on, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35" requirePermission="false" allowDefinition="MachineTo Applicatio n"/>
<section name="roleService" type="System.Web.Configura tion.Scrip tingRoleSe rviceSecti on, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35" requirePermission="false" allowDefinition="MachineTo Applicatio n"/></sect ionGroup>< /sectionGr oup></sect ionGroup>< /configSec tions><app Settings/>
<connectionStrings/>
<system.web>
<!--
Set compilation debug="true" to insert debugging
symbols into the compiled page. Because this
affects performance, set this value to true only
during development.
-->
<compilation debug="true">
<assemblies>
<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C56193 4E089"/>
<add assembly="System.Web.Exten sions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/>
<add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C56193 4E089"/>
<add assembly="System.Data.Data SetExtensi ons, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C56193 4E089"/></ assemblies ></compila tion>
<!--
The <authentication> section enables configuration
of the security authentication mode used by
ASP.NET to identify an incoming user.
-->
<globalization
culture="auto:en-US"
uiCulture="auto:en-US"/>
<authentication mode="Forms">
<forms loginUrl="Special/login.as px" name=".EAGLEEYES" requireSSL="false" defaultUrl="General/Home.a spx" cookieless="AutoDetect" protection="All"/>
</authentication>
<authorization>
<allow users="?"/>
</authorization>
<!--
The <customErrors> section enables configuration
of what to do if/when an unhandled error occurs
during the execution of a request. Specifically,
it enables developers to configure html error pages
to be displayed in place of a error stack trace.
<customErrors mode="RemoteOnly" defaultRedirect="GenericEr rorPage.ht m">
<error statusCode="403" redirect="NoAccess.htm" />
<error statusCode="404" redirect="FileNotFound.htm " />
</customErrors>
-->
<pages>
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Exten sions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/>
<add tagPrefix="asp" namespace="System.Web.UI.W ebControls " assembly="System.Web.Exten sions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/></ controls>< /pages>
<httpHandlers>
<remove verb="*" path="*.asmx"/>
<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Se rvices.Scr iptHandler Factory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/>
<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Se rvices.Scr iptHandler Factory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/>
<add verb="GET,HEAD" path="ScriptResource.axd" validate="false" type="System.Web.Handlers. ScriptReso urceHandle r, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/></ httpHandle rs>
<httpModules>
<add name="ScriptModule" type="System.Web.Handlers. ScriptModu le, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/></ httpModule s></system .web>
<location path="General">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CSharp.CSh arpCodePro vider,Syst em, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c56193 4e089" warningLevel="4">
<providerOption name="CompilerVersion" value="v3.5"/>
<providerOption name="WarnAsError" value="false"/></compiler> </compiler s></system .codedom>
<system.webServer>
<validation validateIntegratedModeConf iguration= "false"/>
<modules>
<remove name="ScriptModule"/>
<add name="ScriptModule" preCondition="managedHandl er" type="System.Web.Handlers. ScriptModu le, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/></ modules>
<handlers>
<remove name="WebServiceHandlerFac tory-Integ rated"/>
<remove name="ScriptHandlerFactory "/>
<remove name="ScriptHandlerFactory AppService s"/>
<remove name="ScriptResource"/>
<add name="ScriptHandlerFactory " verb="*" path="*.asmx" preCondition="integratedMo de" type="System.Web.Script.Se rvices.Scr iptHandler Factory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/>
<add name="ScriptHandlerFactory AppService s" verb="*" path="*_AppService.axd" preCondition="integratedMo de" type="System.Web.Script.Se rvices.Scr iptHandler Factory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/>
<add name="ScriptResource" verb="GET,HEAD" path="ScriptResource.axd" preCondition="integratedMo de" type="System.Web.Handlers. ScriptReso urceHandle r, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD3 64E35"/></ handlers>< /system.we bServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microso ft-com:asm .v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Extension s" publicKeyToken="31bf3856ad 364e35"/>
<bindingRedirect oldVersion="1.0.0.0-1.1.0. 0" newVersion="3.5.0.0"/></de pendentAss embly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Extension s.Design" publicKeyToken="31bf3856ad 364e35"/>
<bindingRedirect oldVersion="1.0.0.0-1.1.0. 0" newVersion="3.5.0.0"/></de pendentAss embly></as semblyBind ing></runt ime></conf iguration>
System.Web.Security.FormsA
Response.Redirect("~/Gener
Logout --------------------------
Session.Abandon();
Session.Clear();
Session.Contents.RemoveAll
System.Web.Security.FormsA
Response.Redirect("~/Publi
Web Config --------------------------
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.extension
<sectionGroup name="scripting" type="System.Web.Configura
<section name="scriptResourceHandle
<sectionGroup name="webServices" type="System.Web.Configura
<section name="jsonSerialization" type="System.Web.Configura
<section name="profileService" type="System.Web.Configura
<section name="authenticationServic
<section name="roleService" type="System.Web.Configura
<connectionStrings/>
<system.web>
<!--
Set compilation debug="true" to insert debugging
symbols into the compiled page. Because this
affects performance, set this value to true only
during development.
-->
<compilation debug="true">
<assemblies>
<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C56193
<add assembly="System.Web.Exten
<add assembly="System.Xml.Linq,
<add assembly="System.Data.Data
<!--
The <authentication> section enables configuration
of the security authentication mode used by
ASP.NET to identify an incoming user.
-->
<globalization
culture="auto:en-US"
uiCulture="auto:en-US"/>
<authentication mode="Forms">
<forms loginUrl="Special/login.as
</authentication>
<authorization>
<allow users="?"/>
</authorization>
<!--
The <customErrors> section enables configuration
of what to do if/when an unhandled error occurs
during the execution of a request. Specifically,
it enables developers to configure html error pages
to be displayed in place of a error stack trace.
<customErrors mode="RemoteOnly" defaultRedirect="GenericEr
<error statusCode="403" redirect="NoAccess.htm" />
<error statusCode="404" redirect="FileNotFound.htm
</customErrors>
-->
<pages>
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Exten
<add tagPrefix="asp" namespace="System.Web.UI.W
<httpHandlers>
<remove verb="*" path="*.asmx"/>
<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Se
<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Se
<add verb="GET,HEAD" path="ScriptResource.axd" validate="false" type="System.Web.Handlers.
<httpModules>
<add name="ScriptModule" type="System.Web.Handlers.
<location path="General">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CSharp.CSh
<providerOption name="CompilerVersion" value="v3.5"/>
<providerOption name="WarnAsError" value="false"/></compiler>
<system.webServer>
<validation validateIntegratedModeConf
<modules>
<remove name="ScriptModule"/>
<add name="ScriptModule" preCondition="managedHandl
<handlers>
<remove name="WebServiceHandlerFac
<remove name="ScriptHandlerFactory
<remove name="ScriptHandlerFactory
<remove name="ScriptResource"/>
<add name="ScriptHandlerFactory
<add name="ScriptHandlerFactory
<add name="ScriptResource" verb="GET,HEAD" path="ScriptResource.axd" preCondition="integratedMo
<runtime>
<assemblyBinding xmlns="urn:schemas-microso
<dependentAssembly>
<assemblyIdentity name="System.Web.Extension
<bindingRedirect oldVersion="1.0.0.0-1.1.0.
<dependentAssembly>
<assemblyIdentity name="System.Web.Extension
<bindingRedirect oldVersion="1.0.0.0-1.1.0.
Interesting authorization section, typically I see this,
<authorization>
<deny users="?" />
<allow users="*"/>
</authorization>
This denies unauthenticated users, and allows anyone left.
My guess is the Login control is ultimately your problem, since it tries to do a lot of things automatically for you. Have you tried to use a completely manual process?
<authorization>
<deny users="?" />
<allow users="*"/>
</authorization>
This denies unauthenticated users, and allows anyone left.
My guess is the Login control is ultimately your problem, since it tries to do a lot of things automatically for you. Have you tried to use a completely manual process?
ASKER
Actually, the login control only handles data collection - the authenticate event is used if the Login button is clicked but, even in that case, it calls the same authentication code (the only difference is that the event's authenticate flag is set). Regardless of the path, though, I get the same behavior.
The confusion on the auth section may be that there are two different ones. The first is for the publicly accessible areas and the second is to protected the folder structure that contains the protected files.
The confusion on the auth section may be that there are two different ones. The first is for the publicly accessible areas and the second is to protected the folder structure that contains the protected files.
And you're sure the Authenticate method is being called on the second login attempt? Response.Redirect is a pretty solid method, and once you call it, there isn't a way to undo it, or for futher code to stop it.
ASKER
Everything goes through my Authenticate() and, yes, I have verified that it is always called. My guess is that either the authentication is not being recorded somewhere in the internals of FormsAuthentication, that there is some mechanism that is overriding the Redirect (my code terminates after this call so I'm not directly overiding it and, based on the call stack, the same chain of events occurs up to that point in both cases), or that some state is inconsistent after the logout.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
After tracing the headers and not seeing anything unusual, I went back at looked at the entire process more closely and finally found the cause of the problem. There are a set of cookies still being passed around that I thought were deleted. These cookies direct the app to logout the user (go figure).
So, the question is now "how do I get rid of these dang cookies once I don't need them?" I thought that Response.Cookies.RemoveAll () would do that but these, in effect, originate at the client (i.e. passed back) and so they are not in the Response list. I also tried removing them from the Request assuming that this would prevent them from being populated back into the Response but that didn't work.
I can set a short expiration and work around the problem but I'd prefer not to play Russian Roulette with timing. What is the correct way to get rid of cookies?
So, the question is now "how do I get rid of these dang cookies once I don't need them?" I thought that Response.Cookies.RemoveAll
I can set a short expiration and work around the problem but I'd prefer not to play Russian Roulette with timing. What is the correct way to get rid of cookies?
Use javascript, I've written javascript in a many logout pages to actually delete the cookies, because I noticed Response.Cookies.RemoveAll didn't work at all.
ASKER
Would you happen to have JS handy to delete them?
It's as simple as setting the expiration to a past date. This immediately deletes it.
function del_cookie(name) {
document.cookie = name +
'=; expires=Thu, 01-Jan-70 00:00:01 GMT;';
}
function del_cookie(name) {
document.cookie = name +
'=; expires=Thu, 01-Jan-70 00:00:01 GMT;';
}
ASKER
Thanks for the help.
ASKER
Just to follow-up: The final solution was not to set the expiration via javascript. Using this technique in IE, the JS actually created a second cookie rather than setting the expiration on the first. In Safari, it did nothing at all.
The final solution was to set the expiration on the server *after* processing the cookie (i.e. the final segment of the roundtrip from server->client->server) and then to manually add the cookie into the Response object.
- if you set the expiration at the first step, it will not work if the client's and server's clocks are not synced. I also added a server-side check to see if the cookie has expired incase the client's clock is out of sync enough that the cookie doesn't expire after another round trip.
- if you do not manually add the cookie into the Response, the changes to the expiration will not propogate
The final solution was to set the expiration on the server *after* processing the cookie (i.e. the final segment of the roundtrip from server->client->server) and then to manually add the cookie into the Response object.
- if you set the expiration at the first step, it will not work if the client's and server's clocks are not synced. I also added a server-side check to see if the cookie has expired incase the client's clock is out of sync enough that the cookie doesn't expire after another round trip.
- if you do not manually add the cookie into the Response, the changes to the expiration will not propogate
You might want to try setting defaultUrl in web.config (same place you set the logonUrl) and see what happens.