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

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.)

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.

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.

Related

visitor id mid is not consistent across domains when i login from app and then from moving from app to web

Experience cloud vistor id is used. App, web are using same adobe launch library. When I login into app url change and mid changes and then if I navigate from app to web responsive page mid is changed and I am not seeing any cross-domain pathing report from app to web ?
anything do I need to do with s.cookiedomainperiod or anything to make this work?
The Experience Cloud Visitor ID is not automatically carried over from the native mobile app to a (mobile) web page. The long story short is native apps don't really store data locally in the same way as web browsers, so there's no automatic ability to use the same local storage mechanism/source between the two.
In order to do this, you must add some code to the mobile app to append the mid value to the target URL, e.g. :
Android
String urlString = "http://www.example.com/index.php";
String urlStringWithVisitorData = Visitor.appendToURL(urlString);
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlStringWithVisitorData));
startActivity(browserIntent);
iOS
NSURL *url = [NSURL URLWithString:#”http://www.example.com/index.php"];
NSURL *urlWithVisitorData = [ADBMobile visitorAppendToURL:url];
[[UIApplication sharedApplication] openURL:urlWithVisitorData];
If implemented properly, you should now see a adobe_mc= parameter appended to the target URL. Then on page view of the target page, if you have the Adobe Analytics javascript and Experience Cloud Visitor ID libraries implemented, they will automatically look for and use that value instead of generate a new value (should not require any config / coding on this end).
Update:
#Ramaiyavraghvendra you made a comment:
Hi #Crayon, mny thanks for your profound answer. I am sorry that i
missed to inform that this app is not native one but this is a SPA
app. so the implementation of entire app is also done through launch.
Could you pl help in this case then.
I'm not entirely sure I understand your issue. If you are NOT moving from a native mobile app to web page, and your mobile app is really a web based SPA that outputs Launch as regular javascript code throughout the entire app, then you shouldn't have to do anything; the Experience Cloud ID service should carry over the id from page to page.
So it sounds to me like perhaps your Experience Cloud Visitor ID and/or Adobe Analytics collection server settings are not configured correctly. the cookie domain period variables may be an issue, if logging in involves moving from say www.mysite.com to www.mysite.co.uk or similar, but shouldn't be a problem if the TLD has the same # of periods.
Or, the trackingServer and trackingServerSecure variables may not be configured properly. In practice, I usually do not set trackingServerSecure at all. These variables get kind of confusing and IMO buggy in different scenarios vs. what you are using, so I tend to use the "secure" value in the trackingServer field and leave the trackingServerSecure blank, and then Experience Cloud Visitor ID and Adobe Analytics will just use the secure version 100% of the time.
Or..it could be a number of other config variables not properly set. It's hard to say if any of this is off, without access to the app and Launch container.
Also you may want to check the response headers for your logged in pages. It may be that they are configured to reject certain existing non-https cookies or something else that effectively causes the existing cookies to be unreadable and make the Experience Cloud ID service generate a new ID and cookies.
Or.. maybe your app kind of is a native mobile app but using an http wrapper to pull in web pages, so it is basically a web browser but it is effectively like moving from one web browser to another (e.g. starting on www.site.com/pageA on Chrome, and then copy/pasting that URL over to Internet Explorer to view). So effectively, different cookie jar.
Launch (or DTM) + Experience Cloud ID (Javascript methods)
In cases such as the last 2 paragraphs, you have to decorate your target links the same as my original answer, but using the Launch + Experience Cloud ID Service javascript syntax:
_satellite.getVisitorId().appendVisitorIDsTo('[your url here]');
You write some code to get the target URL of the link. Then run it through this code to return the url with the parameters added to them, and then you update your link with the new URL.
Super generic example that just updates all links on the page. In practice, you should only do this for relevant link(s) the visitor is redirected to.
var urls = document.querySelectorAll('a');
for (var i = 0, l = urls.length; i < l; i++) {
if (urls[i].href) {
urls[i].href = _satellite.getVisitorId().appendVisitorIDsTo(urls[i].href);
}
}

How to configure ASP.NET Identity with Facebook login - strict redirect

In March of 2018 Facebook began requiring "Strict Mode" for redirect URIs. This means any redirect URI needs to be known in advance, and added to your app's profile (Valid OAuth Redirect URIs). The problem is when the redirect URI contains a dynamic parameter, like a state variable, guid, or user id, etc.
The answer I have found on many websites is that you can hold dynamic state in a "state" parameter. So if your allowed redirect uri is "www.example.com/signin" then this will also allow "www.example.com/signin?state=12344". However, this seems to be no longer allowed. The state parameter no longer seems to be ignored by this strict rule - it fails validation (there is a valid url checker on your facebook app settings page). To test this, I simply put "a.com" in the allow uri field, and "a.com?state=x" in the Redirect URI Validator, and it fails. Without the ?state-x it succeeds. What am I missing?
So I have two questions:
1) First, how to I find out what redirect url my app is actually sending to FB? I can't sniff my traffic since it's https (also required now by fb). I think I know what it is (https://www.example.com/signin-facebook?state=xxxxxx) but I can't be sure and there is no way to verify. The logic that calls FB is wrapped up in the MS Identity library.
2) Second, if it's the state parameter that is causing my login fails, is there any way to disable that in my ASP.NET Core app, or allow it in FB?
Just to clarify, here are two images showing that without the state parameter, the url is valid, but adding the state parameter makes it an invalid URL. Clearly the 'state' parameter is not being ignored by this strict rule checker, as many people have claimed. If I add the state param url to the list of allowed urls, it works but only with that exact state value, not with any different value.
The state parameter is dynamic to begin with (or at least it should be, because its original purpose is CSRF protection), so it is not taken into account when the URL is checked for a “strict” match to the one you have configured.
If you want to use
https://www.example.com/signin-facebook?state=xxxxxx
then configure
https://www.example.com/signin-facebook
as your Valid OAuth Redirect URI.
If you have other (static!) parameters besides the dynamic state, then those must be input into the field as well. You want to use
https://www.example.com/signin-facebook?action=foobar&state=xxxxxx
then the URL in your settings needs to be
https://www.example.com/signin-facebook?action=foobar
And since, as mentioned, the original purpose of this parameter is CSRF protection, it might still be a good idea to add a “random“ component to it, if the actual value you are trying to transport via it is “guessable”, or from a limited range of pre-defined values only.
In that case, I would probably go with an encoded JSON object as the parameter value -
state={"mystate":"foobar","random":8473628}
(Don’t forget to apply proper URL encoding, if your system doesn’t do that automatically.)

Play Framework: Don't change URL after form validation failed

In a plain Play application I have the following scenario.
A route file which looks like this:
GET /accounts/add controllers.Accounts.add()
POST /accounts controllers.Accounts.create()
The first route results in a view where I can add a new account. The form to submit the new account looks something like this:
#helper.form(action = routes.Accounts.create()) {...}
Now the controller binds the input to the form and checks for any validation errors:
public static Result create() {
Form<Account> form = Form.form(Account.class).bindFromRequest();
if (form.hasErrors()) {
return badRequest(views.html.account.add.render(form));
}
...
}
Now the thing is, the client will see the same view with some additional error messages. However, meanwhile the URL has changed from http://example.com/accounts/add to http://example.com/accounts.
If the client now reloads the browser this calls GET http://example.com/accounts (which isn't even mapped in this scenario - thus getting a 404 - Not Found).
Maybe it's just me but I find this kind of annoying and browsing some GitHub projects I couldn't find a good solution for this case.
Of cause things would be much simpler if the second route is rewritten to:
POST /accounts/add controllers.Accounts.create()
... in which case everything works fine. But from a REST point of view this doesn't feel good either. The same applies to update scenarios (having GET /accounts/:id/update vs. PUT /accounts/:id).
Is there a guide on how to handle this? Am I getting something wrong or is this no problem at all (from a pragmatic point of view)?
It's not possible to leave the previous URL because a request for a new address has already been made. A controller only provides response for a requested resource. To go to the previous URL you could only make a redirect in case of validation failure but you would lost errors that way so this is not a solution.
I suggest mapping both actions with the same URL. This way you would solve problem with the browser reload.
If you create a REST service for http clients that aren't browsers you will probably want to serve different response than a simple http page. The separation of actions for particular clients could be a good solution for keeping REST API clean and a browser user happy.

The channel file and CDN

My main javascript (and so all the static content) is included from https://static.anuary.com. The URL that user is browsing is https://dev.anuary.com/somepath/.
Where does the channel file need to be, dev.anuary.com or static.anuary.com? At the moment it is http://static.anuary.com/channel.html.
I am asking because I am still getting the error that says:
Unsafe JavaScript attempt to access frame with URL
https://www.facebook.com/dialog/oauth?access_token=[..]&api_key=[..]&app_id=[..]&client_id=[..]&display=popup&domain=dev.anuary.com&locale=en_US&origin=1&redirect_uri=http%3A%2F%2Fstatic.ak.facebook.com%2Fconnect%2Fxd_arbiter.php%3Fversion%3D6%23cb%3Df3ac7a0544%26origin%3Dhttp%253A%252F%252Fdev.anuary.com%252Ff373e908a8%26domain%3Ddev.anuary.com%26relation%3Dopener%26frame%3Df312def42c&response_type=token%2Csigned_request&scope=email%2Cuser_birthday%2Cuser_likes%2Cuser_interests%2Cpublish_stream&sdk=joey
from frame with URL
http://dev.anuary.com/658fe23c24564ac978c31d167549c4ce8b36686d65a78ad99bfb38765d46e232/.
Domains, protocols and ports must match.
In response to #dtbarne:
Well, that's the thing – I still don't know, because I've tried bowth ways and any in case it produces the same error:
Unsafe JavaScript attempt to access frame with URL
https://www.facebook.com/dialog/oauth?access_token=[..]&api_key=[..]&app_id=[..]&client_id=[..]&display=popup&domain=dev.anuary.com&locale=en_US&origin=1&redirect_uri=http%3A%2F%2Fstatic.ak.facebook.com%2Fconnect%2Fxd_arbiter.php%3Fversion%3D6%23cb%3Df1ee31b93%26origin%3Dhttp%253A%252F%252Fdev.anuary.com%252Ff9359b2f%26domain%3Ddev.anuary.com%26relation%3Dopener%26frame%3Df18e94f9c4&response_type=token%2Csigned_request&scope=email%2Cuser_birthday%2Cuser_likes%2Cuser_interests%2Cpublish_stream&sdk=joey
from frame with URL
http://dev.anuary.com/658fe23c24564ac978c31d167549c4ce8b36686d65a78ad99bfb38765d46e232/.
Domains, protocols and ports must match.
As you said yourself, this error is not fatal, and is meant is a warning to the user that something fishy might be happening.
Facebook also marked this issue as By Design in their bugs system: Bugs > Unsafe JavaScript attempt to access frame with URL....
Also, there are plenty of threads here on stack overflow about this, for example: Facebook Authentication - Unsafe JavaScript attempt to access frame with URL.
Now that we know that this "error message" is unavoidable, use the channel as you wish, as long as everything works for you.
You don't even need to use the channel, as it states in the documentation:
The channelUrl parameter is optional, but recommended. Providing a
channel file can help address three specific known issues. First,
pages that include code to communicate across frames may cause Social
Plugins to show up as blank without a channelUrl. Second, if no
channelUrl is provided and a page includes auto-playing audio or
video, the user may hear two streams of audio because the page has
been loaded a second time in the background for cross domain
communication. Third, a channel file will prevent inclusion of extra
hits in your server-side logs. If you do not specify a channelUrl, you
can remove page views containing fb_xd_bust or fb_xd_fragment
parameters from your logs to ensure proper counts.
But it also states that:
The channel file addresses some issues with cross domain communication
in certain browsers.
So it's up to you to decide.
I personally recommend to use the channel and to serve the file from your regular server and not from the static servers.
You should however set the output to have a long caching expiration date so that your server won't get a lot of requests for this file, as they mention in the JS SDK documentation page:
<?php
$cache_expire = 60*60*24*365;
header("Pragma: public");
header("Cache-Control: max-age=".$cache_expire);
header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$cache_expire) . ' GMT');
?>
<script src="//connect.facebook.net/en_US/all.js"></script>
(php example, but can be done with any language).
Hope this answers clarifies things for you.
Sounds like you know the answer already. It has to be on dev.anuary.com. The purpose is to allow for communication between FB and the URL the user accessed.
The channel file should be places on the server / address the user visits, as the channel file is used to fix some cross-site javascript errors. If the user is always accessing your website using HTTPS, then you should reference the channel file using HTTPS as well as any other content.
The channel file itself should stay as <script src="//connect.[..], but make sure all your static content and JS Includes all have https in them.

Redirecting requests form a catch-all controller in Zend Application without looping forever

There are plenty of related posts to what I'm asking, but after some lengthy searches couldn't quite find what I was looking for, my apologies if it exists somewhere.
My goal -- ALL requests to my Zend App must go through a preDispatch plugin, then pass to a custom Auth controller that will decide whether existing auth credentials are sufficient for the requested operation. 'Sufficient' depends on the logic of the app, hence why I want to do this at the controller+model level. If they suffice, they send the original request along to the specified controller+action, if not they default to a 'get lost' action.
At present I'm using an auth custom plugin set in the preDispatch to simply check for POST'ed auth credentials (if we are logging in), then in all cases the plugin stores the original request and redirects everyone (auth'd or not) to my auth controller, a-la:
$request->setModuleName('default')
->setControllerName('auth')
->setActionName('check')
->setParam('oreq',$request->getParams());
My problem/question is that within my auth->check action, how should I perform the redirect after a decision is made? If I use:
$this->_helper->redirector($or['action'], $oreq['controller']);
then I obviously get an infinite loop as these requests pass through the preDispatch plugin again. Sure I could pass something with the redirect so that the Auth plugin ignores such requests, but this is clearly a security hole. I had thought about maybe generating and storing an md5 hash, storing it to session and passing that as an escape param, but that seems a little sketchy.
Any better ideas out there? Perhaps a redirect method that does not go through the standard predispatch routine in Zend App? Thanks in advance!
This is not how it is done usually in Zend Framework. Not all requests go to a common place and gets redirected to the original requested place authentication.
For access control, use Zend_Acl. Through that, you could easily determine whether the current user has the necessary auth to access the content, else redirect to 'get lost' action.
If you are still adamant on using your technique, use _forward method instead of redirect method.
Since _forward is an internal redirect, you could pass additional arguments and check that in preDispath to avoid a loop.
$this->_forward($action, $controller, $module, $params)