ASP.NET WebForms: Ignore third-party viewstate



  • Situation: We have an ASP.NET application that is built in .NET 4.0 using WebForms. This application offers the ability for a "remote login", which is really just the third-party site POSTing to our site with the login credentials and a verification key.

    Problem: If the third-party also uses ASP.NET WebForms and does the POST from a Form with runat="server" set, their viewstate is passed onto our side, and ASP.NET attempts to handle it automatically and throws "Validation of viewstate MAC failed", because our side can't decrypt the viewstate they pass because the machine keys don't match.

    Workaround: Currently, the only workaround we have is to require the third-parties who would do the remote login is to either:

    1. Not do it with ASP.NET WebForms
    2. Explicitly remove the viewstate from the Form using Javascript before POSTing

    I was attempting to look around to see if there is any other option which would not require making those requests of the third-party without also getting the following "options":

    1. Use ASP.NET MVC
    2. Don't use ASP.NET

    It's not a deal killer to have to give those workarounds to the third-party, but I was just wanting to have something in place so we don't have to worry about needing those workarounds for this situation.



  • Should be of some interest. It'll mitigate the MAC verification problem, but I don't know how happy ASP.NET is with having a totally out-of-the-blue viewstate come back to it.



  • First thing I saw on that article:

    This attribute should never be set to false in a production Web site, even if the application or page does not use view state. The view state MAC helps ensure the security of other ASP.NET functions in addition to view state.

    Emphasis added.

    I'll have to give it a look when I'm at work tomorrow to see if it will work and if that property will cause any other complications.



  • The viewstate verification is there for security purposes. Working around it isn't the best idea, if it can be avoided.[1]

    I would create a web service within your application that can handle the authentication requests. Then, have any third party requests hook into the web service instead of your login page. In order to minimize code duplication, you could have your login page hook into that as well.

    This may be difficult to implement if you are using a membership provider to handle the authentication.

    [1] (edit) Essentially, the viewstate verification is there to prevent phishing scams. You know, the kind where someone mimics your site to collect the security data, while posting everything to your actual site for verification.



  • @abarker said:

    The viewstate verification is there for security purposes. Working around it isn't the best idea, if it can be avoided.

    I guess one thing I didn't think to mention was: this application isn't entirely new, it's rebuilding/upgrading an already existing and fairly heavily used version written in .NET 1.1. The big reasons for upgrading:

    • .NET 1.1 doesn't install too well on Server 2008 R2, and we didn't even want to try on 2012 which became our requirement
    • Server 2003 (insert many reasons here), but specifically SSL/TLS concerns

    Said existing version has the same remote login feature, and it is done the same way (by taking a POST). It also has the same problem and we wouldn't be porting back any fixes, but there can be new users that will use this app that may want a remote login, so I'd like to try and "future proof" it.

    @abarker said:

    web service within your application that can handle the authentication requests

    While I could, I'm still unfortunately burdened by the "drop in replacement" issue, though the actual login logic is not in the Login page itself. We're handling the login on the site via AJAX to an ASHX which has the login process as a shared method. And I'm honestly not sure how well doing a web service version will go, because the users we have/will get will prefer the simplest effort on their part (e.g. least money spent for their web designers to implement the remote login on their site).

    Also, the login itself is not necessarily just username/password, it can also require a security token at the time of entering the password, it can also require answering a security question after providing the password, and you may have a prompt you have to read and acknowledge before actually entering.

    Right now, the remote login just handles the user/pass/token, then either puts you into the logged in section or drops you on the login screen ready to handle the security question and/or prompt.


    *sigh* I am realizing as I'm responding just how much information I left out that could've helped that I didn't even think of throwing in. Hindsight is 20/20 and all that.



  • @ChaosTheEternal said:

    And I'm honestly not sure how well doing a web service version will go, because the users we have/will get will prefer the simplest effort on their part (e.g. least money spent for their web designers to implement the remote login on their site).

    Shouldn't be an issue. .NET web services take POST requests, same as your current method. Would be a similar amount of work for third parties.

    @ChaosTheEternal said:

    Also, the login itself is not necessarily just username/password, it can also require a security token at the time of entering the password, it can also require answering a security question after providing the password, and you may have a prompt you have to read and acknowledge before actually entering.

    Right now, the remote login just handles the user/pass/token, then either puts you into the logged in section or drops you on the login screen ready to handle the security question and/or prompt.

    So when they get redirected, are they getting redirected to pages you control?



  • @abarker said:

    Shouldn't be an issue.

    The clients of the client aren't the only cheap ones. The client is also wanting to minimize costs, so most of what I'm doing is just upgrading and only really changing things where it breaks the new look and feel they're wanting. Except where some of their employees (who are "testing") are bringing up things to change and the boss at the client is agreeing, but that's for another time.

    @abarker said:

    are they getting redirected to pages you control?

    If the login would be successful, yes, it will be to a page in our application. If the login would fail (user/pass/token are invalid), they would be sent back to the third-party's website.



  • I agree with abarker, make a quick-and-dirty .ASHX page to handle auth.



  • If the login process happens in an ASHX then your already half way there. An ASHX can take and return whatever you want so basically is a web service.

    I always thought ASHX pages were slept on back in web forms.


  • Java Dev

    @ChaosTheEternal said:

    If the login would be successful, yes, it will be to a page in our application. If the login would fail (user/pass/token are invalid), they would be sent back to the third-party's website.

    Isn't that an information leak? As I understand it result of a login should be a pure boolean - either success or failure - or the value of any multi-factor security is severely diminished.



  • Aren't they violating your spec? Your spec says to POST three fields - username, password, and token (or whatever). They are posting __VIEWSTATE in addition to those fields which causes the problem.

    Also, they are doing WebForms wrong. ASP.Net WebForms should always post back to themselves. If they are sending their own post to a third-party site, this behavior is expected. Does their form sometimes post back and sometimes post to you? If so, that's the WTF. If they always post to you, then simply removing runat="server" would solve the problem.



  • @PleegWat said:

    Isn't that an information leak?

    This is how the old system is, and I'm tasked with updating it without breaking what the clients of the client have.

    @PleegWat said:

    result of a login should be a pure boolean

    If the credentials are valid, you're "logged in", though there may be another security question you have to pass or a prompt to answer before you're actually allowed in, but by that point, you're in our application.

    If the credentials aren't valid, we Response.Redirect back to the third-party site, and the most we pass back is an error message via the query string, which is the same error message you'd get if you failed to log in locally.



  • @Jaime said:

    Aren't they violating your spec?

    It's hypothetical. Right now we don't have a third-party with this issue, I was just intending to try and add some future-proofing.

    @Jaime said:

    Also, they are doing WebForms wrong.

    I wouldn't be denying that, but at the same time, due to a remote login we have to do in the application, I end up doing the WebForms a bit wrong too (though in my case, I have it scrub all fields except the ones we'd pass to the third party, but I'm stuck in a MasterPage that has an encompassing <form runat="server"> that I have to tweak with Javascript).

    @Jaime said:

    Does their form sometimes post back and sometimes post to you?

    Again, hypothetical, but right now, to my knowledge, all that have implemented it have a form only for posting to us.



  • @Jaime said:

    Also, they are doing WebForms wrong.

    You know what, for my own sanity, and for the fact that I have enough stuff to work on without adding more to my plate, I'll probably just leave it as it is and if they do a WebForms POST to us wrong, they just get our error page.

    Thanks everyone.



  • Just give them a bit of sample code that works regardless of the current page. Maybe something like:

    $('.something').on('click', function() {
      $('<form class="hidden-form" action="http://yoursite/partnerlogin.aspx" method="post" style="display: none;">' + 
        '<input type="text" name="username" />' +
        '<input type="text" name="password" />' +
        '<input type="text" name="token" /></form>')
        .appendTo('body');
      $('[name="username"]').val(userName);
      $('[name="password"]').val(password);
      $('[name="token"]').val(token);
      $('.hidden-form').submit();
    });
    
    

Log in to reply