URL fragment lost as part of SAML token authentication; workaround / standard pattern?

3.2k views Asked by At

Several web application authentication protocols (like WS-Federation and the SAML protocol, i.e., so-called 'passive' protocols, and apparently also ASP.NET Forms authentication, see this StackOverflow question, and AppEngine, see this GWT bug comment) lose the original 'URL fragment', i.e. the part after the #-sign.

What happens is roughly the following: in a clean browser (so no cached info/cookies/login information) I open URL (1) http://example.com/myapp/somepage?some=parameter#somewhere. This makes the browser request (2) http://example.com/myapp/somepage?some=parameter, the server redirects me to my identity provider (including URL (2) in the authentication request), and ultimately I'm redirected back to where I came from, which is URL (2): that is the only URL that the server knows about. But I wanted to go to URL (1), and the URL fragment ('anchor') has been lost along the way, actually in the first step already.

This seems to be a fundamental limitation of these protocols, since the server never sees the URL fragment at all.

I know that it according to specifications that the browser requests (2) from the server, when I navigate to (1), leading to this fragment-losing limitation on the SAML protocol, WS-Federation, etc. My question is: how do I work around this limitation?

The obvious workaround is to avoid URL fragments, as suggested in this answer. However, for our specific web application that is not nice, since we use bookmarkable URL fragments in our single-page GWT application, to make sure that a navigation within our application does not cause the page to reload.

My question: What other workarounds or standard patterns are there for this situation?

(I'm specifically interested in a GWT + SAML protocol solution.)

2

There are 2 answers

1
Kaspars Ozols On

According to RFC 1738 anchor tags are not sent by the client to the server, when requesting for a resource.

Anchor tags are used to identify a location within a resource and not a different resource on the server. In order to identify the location in the resource, the client needs to fetch the complete resource from the server, and this process need not involve transfer of information about the fragment (as it does not mean anything to the server).

If you do wish to send the fragment character (#) to the server, then you'll need to encode it in the query string, or the client(browser) will simply ignore that section of the URL when it sends the request to the server.

EDIT:

I don't know any real solution but to work around this issue you need to save your full return URL (with anchor tags) somewhere on the client side, because server don't know anything about anchors. For that you could use SessionStorage (http://www.w3schools.com/html/html5_webstorage.asp) to temporary store ReturnUrl until login process is completed. Please note that it won't be supported on older browsers (like <= IE7).

In that case workaround would look something like this:

<script>
    if(typeof(sessionStorage) == 'undefined')
    {
        sessionStorage = {
            getItem: function(){},
            setItem: function(){}
        };
    }

    window.onload = function ()
    {
        var key = 'ReturnUrl';

        //try to get last returnUrl with anchors
        var returnUrl = sessionStorage.getItem(key);

        //if we got something, do the navigation
        if(returnUrl !== undefined && returnUrl !== document.URL)
        {
            //clean it up
            sessionStorage.setItem(key, null);
            //navigate to last URL
            window.location = returnUrl;
        }
        else
        {
            //store url 
            sessionStorage.setItem(key, document.URL);
        }
    }
</script>

PS. Bear with me if there are some syntax errors because I wrote it down from top of my head and didn't try it.

0
Thomas Broyer On

You basically have two options:

  • avoid using location.hash (use HTML5's pushState instead, at least on browsers that support it; and/or propose a way to generate permalinks within your app – Google Groups does that)

  • do the redirection using JavaScript. I.e. instead of sending a redirect from the server, send an empty HTML page with some script that takes the full URL (with the hash) and does the redirection using location.assign() or location.replace(). With a bit of luck (depending on the servers), you'll be redirected to that full URL after authentication.

You can of course do both: if the link is a deep-link into the app, then do the redirect (i.e. assume there's no hash), otherwise send a page with JS to make sure you don't lose any state present in the hash.

And finally the obvious third solution, far from ideal: don't do anything, and try to educate users that when they needed to (re)authenticate then they should re-paste the URL or re-click the link or re-click the bookmark.