When you use UserDB cookie authentication to log in on a URL that is a GET form with a query string, the page you were originally going to acts as though the query string weren't there. That's because cgi.FieldStorage only pays attention to the query string as a source of fields if the request is a GET or HEAD, and the default login document for UserDB has a POST method. Further, if you log in to a page that is a POST form, the field data from the original POST will be gone. There is, however, a simple solution to both problems. First, change the ACTION="" attribute of the FORM tag to remove the <!--#if QUERY_STRING-->?<!--#var QUERY_STRING--><!--#/if--> block. It is superfluous, since Zope will subsequently ignore the query string anyway. Second, add the following block somewhere within the FORM block (indentation added for clarity): <!--#in "REQUEST.form.items()"--> <!--#if "_['sequence-key'] not in ('__ac_name','__ac_password')"--> <input type="hidden" name="<!--#var sequence-key html_quote-->" value="<!--#var sequence-item html_quote-->"> <!--#/if--> <!--#/in--> This will add in any form variables from the original request, whether they came from the query string or a POST form. The loop checks and excludes __ac_name and __ac_password from the hidden fields, in case the page is being redisplayed due to a bad login. Note that this is only a partial fix, however. For example, it doesn't check for multi-valued fields such as tokens, lines, lists, etc. And non-string fields (e.g. integers) will turn into strings. :( So short of writing an ExternalMethod to marshal arbitrary objects into hidden fields, this is as good as it gets. If at some point I write code that can do the marshalling, I'll post it here. Meanwhile, this is good enough for the relatively small number of entry points in my existing apps.