ViewStateUserKey and "Invalid_Viewstate" when posting back during Forms Authentication
From my point of view, I like ASP.NET because it's at least consistent. Anytime some crazy crap happens, at least it makes sense once the dust clears.
We've got a "Workflow" ASP.NET Server Control that i s very snazzy. It's a templated user control that handles all the state transitions one has to deal with when showing and hiding different panels during a Wizard-style operation.
So, you can setup Next and Previous or whatever buttons you like, hook each up to any WorkflowStep and there you go. It's a simpler thing that the UIP. Anyway, as with all ASP.NET 1.1 Postback-like things, it postsback to the current page.
Today someone who was creating an Enrollment and Signon workflow got this Exception during a PostBack:
Exception: System.Web.HttpException
Message: Invalid_Viewstate
Source: System.Web
at System.Web.UI.Page.LoadPageStateFromPersistenceMedium()
at System.Web.UI.Page.LoadPageViewState()
at System.Web.UI.Page.ProcessRequestMain()
When you get this exception the standard list of things to check are:
- If this is a cluster, edit <machineKey> configuration so all servers use the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.
- Viewstate can only be posted back to the same page.
- The viewstate for this page might be corrupted.
However, this isn't an error that you'd likely see on a single development ox, so something had to be up. The only interesting and different thing about this situation was that the user, in step 4 of this 7-step process, gets logged into Forms Authentication. When I say "gets logged in" I mean, a cookie is sent to them.
Here's the flow of what happened and why it's a problem:
- Un-Forms-Authenticated Client (no cookies) does a GET and receives some standard ViewState.
- Client fills out form, POSTs back. ViewState is decoded, life goes on.
- During Page processing, user is "logged on" and a Forms Authentication cookie is set out (queued up more like it).
- Additionally more ViewState is sent back, not encrypted.
- Now Forms-Authenticated Client (cookies) fills out form, POSTs back. ViewState from step 4 is returned to the server.
- In Global.AuthenticateRequest(), the cookie from this client is cracked open and a valid SecurityPrincipal is put on the current Thread. Now, User.Identity.IsAuthenticated is true, and User.Identity.Name is set to some string.
- In the Page.Init() this line executes, which I've mentioned before. This adds another layer of protection to the User's ViewState.:
if (User.Identity.IsAuthenticated){ViewStateUserKey = User.Identity.Name;} - Then, after Page.Init() but before Page.Load(), the internal method LoadViewStateFromPersistanceMedium() starts to open up the ViewState that was passed to us. This was the non-protected non-encrypted viewstate from step #4.
- Exception occurs, because we now are trying to decrypt ViewState with a key that wasn't present when the ViewState was originally generated.
Moral: Don't change ViewStateUserKey when there is pending ViewState that hasn't been posted back and cracked open yet.
However, you can ONLY change Page.ViewStateUserKey in Page.OnInit, so we do a Response.Redirect after the Forms Authentication Cookie is sent out. This avoids any potential trouble with logging a user in on a postback, and cleans up the workflow considerably.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
BTW, I did a little research last night, and stumbled across a sample chapter from a security book. It suggested using Session ID or Username for the key if the user is logged in, and using a random number (presumably stored somewhere) if the user isn't authenticated. It strongly recomended avoiding unencrypted viewstates.
That shed some light on a problem I'm having: I've got a form that people sometimes sit on for an hour before submitting. They have to be logged in (FormsAuthentication) to access the page -- but their authentication expires before they submit the form. Now the viewstate info is invalid, because the key has changed. Can you think of a good way to avoid this?
Thanks!
PS: I also found that the comment-spam test requires lowercase entry, even though the picture shows all caps.
Comments are closed.
Nice post. I think using the Session ID for the ViewStateUserKey would solve your problem. It may cause you others though - would you ever want the viewstate to persist between sessions?