Search code examples
asp.net-mvcasp.net-mvc-4simplemembership

SimpleMembership fails first login attempt


My issue is that the new simplemembership provider fails a good username/password combination on the first attempt. The first failed attempt produces the .ASPXAUTH which now contains the user name, the second attempt will work with the same data.

I know this is bound up with the .ASPXAUTH cookie b/c if I delete that after the first (failed) attempt, then I get the same results. The net result is unless it has a user name in the auth cookie that matches the passed in form user name, the person needs to log in a second time. This has nothing to do w/connecting to the database, or initializing the connection b/c I can reproduce this behavior every time.

THoughts?

[HttpPost]
        [AllowAnonymous]
        //[ValidateAntiForgeryToken]
        [HandleError(View = "AntiForgeryExceptionView", ExceptionType = typeof (HttpAntiForgeryException))]
        public ActionResult Login(LoginModel model, string returnUrl)
        {
            ViewBag.Message = "Login";

            if (ModelState.IsValid && WebSecurity.UserExists(model.UserName))
            {

               WebSecurity.Login(model.UserName, model.Password, model.RememberMe);
                if (WebSecurity.CurrentUserId > 0)
                {
                    if (!IsRegistered(WebSecurity.CurrentUserId))
                    {
                        ModelState.AddModelError("", "You cannot log in before registering.");
                        return View(model);
                    }
                    if (HttpContext.Session != null)
                    {
                        Helpers.MemberService.Set(new MemberSession
                                                      {
                                                          UserId = WebSecurity.CurrentUserId,
                                                          SessionId = HttpContext.Session.SessionID
                                                      });
                    }

                    var cookie = new HttpCookie("MemberId")
                    {
                        Expires = DateTime.Now.AddDays(-1) // or any other time in the past
                    };
                    HttpContext.Response.Cookies.Set(cookie);
                    HttpContext.Response.Cookies.Add(new HttpCookie("UserId", WebSecurity.CurrentUserId.ToString(CultureInfo.InvariantCulture)));
                    if (string.IsNullOrEmpty(returnUrl))
                    {
                        // we can assume the user has just logged on...
                        returnUrl = "../Summary.htm";
                    }
                    return RedirectToAction(returnUrl);
                }
            }

            // If we got this far, something failed, redisplay form
            ModelState.AddModelError("", "The user name or password provided is incorrect.");

            return View(model);
        }

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />

    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
    <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core">
      <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" />
      <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" />
      <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" />
      <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" />
    </sectionGroup>
  </configSections>
  <appSettings configSource="Helpers\Config\appsettings.config" />
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <authentication mode="Forms">
      <forms loginUrl="~/Account/Login" timeout="2880" />
    </authentication>
    <pages>
      <namespaces>
        <add namespace="System.Web.Helpers" />
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages" />
      </namespaces>
    </pages>
    <trust level="Full" />
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true" />
    <handlers>
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="StructureMap" publicKeyToken="e60ad81abae3c223" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.6.4.0" newVersion="2.6.4.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="EntityFramework" publicKeyToken="b77a5c561934e089" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="DotNetOpenAuth.AspNet" publicKeyToken="2780ccd10d57b246" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="DotNetOpenAuth.Core" publicKeyToken="2780ccd10d57b246" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
      </dependentAssembly>
    </assemblyBinding>
    <legacyHMACWarning enabled="0" />
  </runtime>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
  </entityFramework>
  <system.net>
    <defaultProxy enabled="true" />
    <settings>
      <!-- This setting causes .NET to check certificate revocation lists (CRL) 
                 before trusting HTTPS certificates.  But this setting tends to not 
                 be allowed in shared hosting environments. -->
      <!--<servicePointManager checkCertificateRevocationList="true"/>-->
    </settings>
  </system.net>
  <dotNetOpenAuth>
    <messaging>
      <untrustedWebRequest>
        <whitelistHosts>
          <!-- Uncomment to enable communication with localhost (should generally not activate in production!) -->
          <!--<add name="localhost" />-->
        </whitelistHosts>
      </untrustedWebRequest>
    </messaging>
    <!-- Allow DotNetOpenAuth to publish usage statistics to library authors to improve the library. -->
    <reporting enabled="true" />
    <openid>
      <relyingParty>
        <security requireSsl="false">
          <!-- Uncomment the trustedProviders tag if your relying party should only accept positive assertions from a closed set of OpenID Providers. -->
          <!--<trustedProviders rejectAssertionsFromUntrustedProviders="true">
                        <add endpoint="https://www.google.com/accounts/o8/ud" />
                    </trustedProviders>-->
        </security>
        <behaviors>
          <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible
                         with OPs that use Attribute Exchange (in various formats). -->
          <add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth.OpenId.RelyingParty" />
        </behaviors>
      </relyingParty>
    </openid>
  </dotNetOpenAuth>
  <uri>
    <!-- See an error due to this section?  When targeting .NET 3.5, please add the following line to your <configSections> at the top of this file:
        <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
        -->
    <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names),
         which is necessary for OpenID urls with unicode characters in the domain/host name. 
         It is also required to put the Uri class into RFC 3986 escaping mode, which OpenID and OAuth require. -->
    <idn enabled="All" />
    <iriParsing enabled="true" />
  </uri>
  <log4net>
    <appender name="GeneralLog" type="log4net.Appender.RollingFileAppender">
      <!--<file value="${TEMP}\\Logs\\AppName_${COMPUTERNAME} " />-->
      <file value="Logs\\WebLog.log" />
      <appendToFile value="true" />
      <rollingStyle value="Composite" />
      <staticLogFileName value="false" />
      <datePattern value=".yyyyMMdd.'log'" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="5MB" />
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%d{HH:mm:ss.fff} [%t] %-5p %c - %m%n" />
      </layout>
    </appender>
    <appender name="DebugAppender" type="log4net.Appender.DebugAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%d [%t] %-5level %logger - %message%newline" />
      </layout>
    </appender>
    <root>
      <level value="ALL" />
      <appender-ref ref="GeneralLog" />
      <appender-ref ref="DebugAppender" />
    </root>
  </log4net>
</configuration>

Solution

  • I think you are missing the following line to authenticate the cookie for the current request:

    FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
    

    EDIT:

    You're using the new WebSecurity security model, not forms authentication. Ignore my first answer.

    Could you check what the contents of WebSecurity.CurrentUserId are after you login? I think the properties of WebSecutiy are not set immediately after the call to the Login. If you send a second request after the call to Login you should see that the CurrentUserId is set.

    If you want to check for a succesfull login before continuing you could do the following:

    bool isLoggedIn = WebSecurity.Login(model.UserName, model.Password, model.RememberMe);
    if (isLoggedIn) {
        ....
    }