Josh Russo
Josh Russo

Reputation: 3241

How to debug a viewstate validation error in DNN?

I have a DotNetNuke application that I inherited and I'm trying to make some updates to an existing module. It happens to be a custom login module.

I'm trying to integrate 2 factor authentication. The login itself works, but when I then want to display the 2 factor fields after that, I get a 503 error on submission of the 2 factor code.

I tried to add an Application_Error event in global.asax, but that was never hit. I also tried to override the OnError event of the login control, but that is also never hit.

The OnInit is always hit during the postback that produces the 503, but not OnLoad. The strangest part is that if I step over each line of the OnInit, until I hit first thing outside of OnInit and then click Continue, everything works fine. If I just click Continue from the breakpoint at the beginning of OnInit, it will fail with a 503 before getting to OnLoad, almost every time.

This points to some kind of asynchronous error but I can't imagine what it might be.

The login versus 2 factor fields are shown and hidden by Panel controls if that makes any difference.

The message that comes back with the 503 error is "No sites currently exist for this installation."

Any suggestions on how to narrow down what's causing the 503 are greatly appreciated.

Update

I missed a log entry for a viewstate validation exception.

Validation of viewstate MAC failed. If this application is hosted by a Web
Farm or cluster, ensure that configuration specifies the same validationKey 
and validation algorithm. AutoGenerate cannot be used in a cluster. 
See http://go.microsoft.com/fwlink/?LinkID=314055 for more information.

I set the machine key values, but I still get the same error. Not to mention that it also doesn't explain why this only happens if I don't step through OnInit

Any thoughts?

Upvotes: 0

Views: 375

Answers (1)

Josh Russo
Josh Russo

Reputation: 3241

It turns out that my problem stemmed from calling UserController.UserLogin() prior to trying to verify the 2 factor authentication.

Now that I'm calling that after the 2 factor verification, everything works as expected, and even consistently :o)

Code

Here's a sample of what changed.

This is what didn't work:

private void ValidateUser(UserInfo objUser, bool ignoreExpiring)
{
    UserValidStatus validStatus = UserValidStatus.VALID;
    string strMessage = Null.NullString;
    DateTime expiryDate = Null.NullDate;
    bool okToShowPanel = true;

    validStatus = UserController.ValidateUser(objUser, PortalId, ignoreExpiring);

    if (PasswordConfig.PasswordExpiry > 0)
    {
        expiryDate = objUser.Membership.LastPasswordChangeDate.AddDays(PasswordConfig.PasswordExpiry);
    }
    UserId = objUser.UserID;

    //Check if the User has valid Password/Profile
    switch (validStatus)
    {
        case UserValidStatus.VALID:
            //check if the user is an admin/host and validate their IP
            if (Host.EnableIPChecking)
            {
                bool isAdminUser = objUser.IsSuperUser || PortalSettings.UserInfo.IsInRole(PortalSettings.AdministratorRoleName); ;
                if (isAdminUser)
                {
                    if (IPFilterController.Instance.IsIPBanned(Request.UserHostAddress))
                    {
                        new PortalSecurity().SignOut();
                        AddModuleMessage("IPAddressBanned", ModuleMessage.ModuleMessageType.RedError, true);
                        okToShowPanel = false;
                        break;
                    }
                }
            }

            //Set the Page Culture(Language) based on the Users Preferred Locale
            if ((objUser.Profile != null) && (objUser.Profile.PreferredLocale != null))
            {
                Localization.SetLanguage(objUser.Profile.PreferredLocale);
            }
            else
            {
                Localization.SetLanguage(PortalSettings.DefaultLanguage);
            }

            //Set the Authentication Type used 
            AuthenticationController.SetAuthenticationType(AuthenticationType);

            var userRequestIPAddress = new UserRequestIPAddressController();
            //Complete Login
            UserController.UserLogin(PortalId, objUser, PortalSettings.PortalName, userRequestIPAddress.GetUserRequestIPAddress(new HttpRequestWrapper(Request)), RememberMe);

            var twoFactorAuthStatus = GetTwoFactorAuthStatus(objUser);

            switch (twoFactorAuthStatus)
            {
                case TwoFactorAuthStatus.Error:
                    return;
                case TwoFactorAuthStatus.NotEnabled:
                    RedirectUser(objUser);
                    break;
                case TwoFactorAuthStatus.SetupNeeded:
                    PageNo = googleAuthSetupPageNo;
                    break;
                case TwoFactorAuthStatus.VerificationNeeded:
                    PageNo = verifyGoogleAuthPageNo;
                    break;
            }
            break;
        case UserValidStatus.PASSWORDEXPIRED:
            strMessage = string.Format(Localization.GetString("PasswordExpired", LocalResourceFile), expiryDate.ToLongDateString());
            AddLocalizedModuleMessage(strMessage, ModuleMessage.ModuleMessageType.YellowWarning, true);
            PageNo = passwordPageNo;
            pnlProceed.Visible = false;
            break;
        case UserValidStatus.PASSWORDEXPIRING:
            strMessage = string.Format(Localization.GetString("PasswordExpiring", LocalResourceFile), expiryDate.ToLongDateString());
            AddLocalizedModuleMessage(strMessage, ModuleMessage.ModuleMessageType.YellowWarning, true);
            PageNo = passwordPageNo;
            pnlProceed.Visible = true;
            break;
        case UserValidStatus.UPDATEPASSWORD:
            AddModuleMessage("PasswordUpdate", ModuleMessage.ModuleMessageType.YellowWarning, true);
            PageNo = passwordPageNo;
            pnlProceed.Visible = false;
            break;
        case UserValidStatus.UPDATEPROFILE:
            //Save UserID in ViewState so that can update profile later.
            UserId = objUser.UserID;

            //When the user need update its profile to complete login, we need clear the login status because if the login is from
            //3rd party login provider, it may call UserController.UserLogin because they doesn't check this situation.
            new PortalSecurity().SignOut();
            //Admin has forced profile update
            AddModuleMessage("ProfileUpdate", ModuleMessage.ModuleMessageType.YellowWarning, true);
            PageNo = profilePageNo;
            break;
    }
    if (okToShowPanel) ShowPanel();
}

This is what worked:

private void ValidateUser(UserInfo objUser, bool ignoreExpiring)
{
    UserValidStatus validStatus = UserValidStatus.VALID;
    string strMessage = Null.NullString;
    DateTime expiryDate = Null.NullDate;
    bool okToShowPanel = true;

    validStatus = UserController.ValidateUser(objUser, PortalId, ignoreExpiring);

    if (PasswordConfig.PasswordExpiry > 0)
    {
        expiryDate = objUser.Membership.LastPasswordChangeDate.AddDays(PasswordConfig.PasswordExpiry);
    }
    UserId = objUser.UserID;

    //Check if the User has valid Password/Profile
    switch (validStatus)
    {
        case UserValidStatus.VALID:
            //check if the user is an admin/host and validate their IP
            if (Host.EnableIPChecking)
            {
                bool isAdminUser = objUser.IsSuperUser || PortalSettings.UserInfo.IsInRole(PortalSettings.AdministratorRoleName); ;
                if (isAdminUser)
                {
                    if (IPFilterController.Instance.IsIPBanned(Request.UserHostAddress))
                    {
                        new PortalSecurity().SignOut();
                        AddModuleMessage("IPAddressBanned", ModuleMessage.ModuleMessageType.RedError, true);
                        okToShowPanel = false;
                        break;
                    }
                }
            }

            var twoFactorAuthStatus = GetTwoFactorAuthStatus(objUser);

            switch (twoFactorAuthStatus)
            {
                case TwoFactorAuthStatus.Error:
                    return;
                case TwoFactorAuthStatus.NotEnabled:
                    LoginUser(objUser);
                    break;
                case TwoFactorAuthStatus.SetupNeeded:
                    PageNo = googleAuthSetupPageNo;
                    break;
                case TwoFactorAuthStatus.VerificationNeeded:
                    PageNo = verifyGoogleAuthPageNo;
                    break;
            }
            break;
        case UserValidStatus.PASSWORDEXPIRED:
            strMessage = string.Format(Localization.GetString("PasswordExpired", LocalResourceFile), expiryDate.ToLongDateString());
            AddLocalizedModuleMessage(strMessage, ModuleMessage.ModuleMessageType.YellowWarning, true);
            PageNo = passwordPageNo;
            pnlProceed.Visible = false;
            break;
        case UserValidStatus.PASSWORDEXPIRING:
            strMessage = string.Format(Localization.GetString("PasswordExpiring", LocalResourceFile), expiryDate.ToLongDateString());
            AddLocalizedModuleMessage(strMessage, ModuleMessage.ModuleMessageType.YellowWarning, true);
            PageNo = passwordPageNo;
            pnlProceed.Visible = true;
            break;
        case UserValidStatus.UPDATEPASSWORD:
            AddModuleMessage("PasswordUpdate", ModuleMessage.ModuleMessageType.YellowWarning, true);
            PageNo = passwordPageNo;
            pnlProceed.Visible = false;
            break;
        case UserValidStatus.UPDATEPROFILE:
            //Save UserID in ViewState so that can update profile later.
            UserId = objUser.UserID;

            //When the user need update its profile to complete login, we need clear the login status because if the login is from
            //3rd party login provider, it may call UserController.UserLogin because they doesn't check this situation.
            new PortalSecurity().SignOut();
            //Admin has forced profile update
            AddModuleMessage("ProfileUpdate", ModuleMessage.ModuleMessageType.YellowWarning, true);
            PageNo = profilePageNo;
            break;
    }
    if (okToShowPanel) ShowPanel();
}

I changed RedirectUser() from the first example, to a LoginUser() that also performs the redirect. The LoginUser() is also called after a successful 2 factor authentication and/or setup as well.

This is a more secure way to handle this in general. The debugging feedback I was receiving was just super frustratingly misleading.

Upvotes: 1

Related Questions