oAuth flow of data from third party domain - javascript

I have made a website that enables users to create their own widgets and place them on their own websites. I want users of those websites to be able to log in to these widgets using Twitter, Facebook and Google. I have 99% of the process in place, but the remaining 1% is my stumbling block. The process I have implemented is as follows:
User creates a widget on my site (mysite.com)
User places some javascript on their own website (example.com) to embed the widget
The user of a widget clicks "Log in with Twitter"
A new window is opened (using window.open) which loads mysite.com/auth/twitter, redirecting them through the oAuth flow on twitter.com
All being well, the user gets redirected back to mysite.com/auth/twitter/callback in the new window and I store the user's details in the database on mysite.com.
At this point, the newly-created User ID should be passed back to the main window in which the widget is embedded. But, as far as I can tell, there is no way to do so because because window.opener in the new window is null (due to the redirects that have happened). Nor do I have a reference from the main window to the new window, also due to the redirects.
I have tried window.postMessage from the new window to the main window, directly accessing functions and variables in the new window from the main window, all to no avail. There is seemingly no way to reference any data or properties from either window.
Is there anything else I can try or do I have to implement the process without a new window somehow?
If it matters, mysite.com is built in Laravel and the social authentication process uses Socialite.
Any help is appreciated!

I have tried window.postMessage from the new window to the main window
postMessage works cross-domain, but you must in that case specify the second parameter (targetOrigin).

Taking into account your clarifications regarding how the widget is embedded, you are fairly limited in regards to communication options as you saw.
Since your child window now has no way to communicate with the parent, being without a window.opener reference and on a different domain, you could rely on the parent and have it track the child window progress like suggested here : https://stackoverflow.com/a/18804255/18706075
Also, not to be insistent on the iframe approach, but you could also add an invisible iframe opened to mysite.com. That way, the pop-up window that ends up on mysite.com has many options to make data available to the iframe on the same domain, and the guest iframe can easily communicate with the host window as I originally suggested :
Host listens to the message event
window.addEventListener("message", myMessageHandler);
Guest iframe notifies the host.
window.top.postMessage({authenticationResult: "whatever_value"}, 'http://example.com');

I couldn’t find a “proper” way to do this, so what I ended up doing was generating a unique code and passing that to the new window. This code gets stored in my database and updated with the ID of the user upon completion of the oAuth process. The main window uses setInterval to check for the existence of a user ID against the unique code every couple of seconds.

POPUPS AND IFRAME REDIRECTS
I would have a look at the oidc-client library, which used to do similar things. See the code in PopupWindow and the way that a named window object is used before the redirect and then used on the response. The OAuth state parameter was used to correlate the request and response:
// Before navigating
window["popupCallback_" + params.id] = this._callback.bind(this);
// Notify opener upon return
var name = "popupCallback_" + data.state;
var callback = window.opener[name];
callback(url, keepOpen);
The library also performed some interesting iframe navigation if you look at the IFrameWindow and IFrameNavigator classes. Using iframes can be permissioned with your own authorization server, but will not work with Google or Twitter due to clickjacking protections.
BROWSER RESTRICTIONS
One of the reasons why the above library is inactive is browser restrictions on content from third party sites, which impacts some of the browser OpenID Connect behaviour.
Your widgets will be treated in a hostile manner by browsers, who will apply the same restrictions as they do on third party ads that try to track users across sites.
In particular, if your widget gets data by calling mysite.com with an HTTP-only cookie, this will be considered a third party cookie and dropped by the browser. Access tokens can work, but they are not considered the browser current best security practice.
MAIN WINDOW REDIRECTS
If you can't get popups to work, consider redirecting on the main window, which will work best from a browser restrictions viewpoint. A main window redirect serves as a user gesture so that any login cookies from Google etc are not dropped. Your flow might work like this, though it requires a design based on access tokens:
Customer page loads at example.com
Widget loads in an iframe at mysite.com and renders content that prompts the user to click a button to authenticate
When the button is clicked, the widget saves the parent URL in session storage, then redirects the parent window to Google, with a mysite com redirect URI
Main window at mysite.com conpletes the login, saves an access token to session storage, then redirects back to the stored parent URL
Customer page loads again at example.com
Widget loads again in an iframe at mysite.com and this time can get an access token from session storage to use for data access
From a user experience viewpoint, the user signs in to the main app initially, then once more to the widget as a separate provider. The impact on the host app is a bit like refreshing the page, which it will already cope with.
FUTURE
The FedCM initiative is aiming to solve this type of cross domain browser identity problem in a future facing way. It will not be ready any time soon, but it is worth reading their docs to identify potential issues with your own solution.

Related

On iOS, is there a way to detect a web page runs in the embedded browser and open the "real" one?

My web site uses magic links for login, however, I have a problem on mobile (not sure about Android - haven't tried yet, but the problem exists at least on iOS): when a user receives the email say in the GMail app, the link opens in the embedded browser, meaning that cookies will not be passed to the "real" browser.
Is there a way to ensure the link in the email opens in the real system browser and therefore cookies are stored permanently?
(Essentially browser session isolation on iOS breaks a lot of things on the Internet, so surely there is a workaround?)
After some research: no, it is not possible to detect you are in an embedded browser, neither is it possible to enforce opening a link in the system one. Makes sense from security point of view.
However, I was asking the wrong question. The problem of a magic link login is solved differently: when starting a login process you can set a session cookie and create an associated DB record for it, marking it as blocked, i.e. not logged in.
At next step, when the magic link is opened in whatever browser you unblock the session in the DB. At this stage you can replace the login cookie with a real one, e.g. JWT, or continue using it as your main auth token.
If the user then returns to the real browser, you check the login cookie and act accordingly, keeping in mind that it may not be the browser where they validated the magic link. At this stage you can, again, replace the login cookie with your real auth cookie knowing that the session has been validated already.
I'm not entirely sure this is 100% safe, need to think about it more but at first glance it does look safe and seems to be pretty much the only way of handling magic links on mobile.

How to allow facebook login from iFrame

My app is widget that is embedded into other website but the one of the most important features is login with facebook.
But now I have a problem.
I have iframe tag but also inside iframe I have:
<a href="http://roomtobid.com/social/login/redirect/facebook" class="btn btn-info btn-lg col-md-12" >Facebook</a>
and now when I try to click and login, just nothing happened.
Also when I look at browser console I get error:
Refused to display
'https://www.facebook.com/login.php?skip_api_login=1&api_key=17499948800008…m5xN3sZ3hrPQWQ44FYQPR1yPpsDwLuvoTP9jtZq6%23_%3D_&display=page&locale=sr_RS'
in a frame because it set 'X-Frame-Options' to 'DENY'.
How I can solve my problem?
Also I try to add target="_top" at <a> tag and that work but then send me to first to facebook and then as callback to original domain not back to iFrame.
Is there any way to solve this?
I see that zopim chat allow their visitors to login with facebook, but their facebook login is opened into new small window.
but then send me to first to facebook and then as callback to original domain not back to iFrame
Of course not.
It only redirects back to the address specified as redirect_uri - and that address has to match the app domain, resp. be specified as a valid OAuth redirect URI in the app settings.
The only thing you can possibly do, is put the URL of the page that the iframe is on, into your apps callback URL as a parameter - so that your app can redirect back there, after Facebook login has redirected back to your app.
You could also try and put it into the session - but that might be problematic if the user has multiple pages opened that embed your widget, plus session cookies and 3rd-party cookie blocking easily cause additional trouble.
And that still leaves your with the problem of getting the "parent" page's URL in the first place. The parent page would have to explicitly transport that information to your widget in the first place, like via a GET parameter that they append to your widget's address in the iframe src. (Either the URL directly, or maybe some sort of client identifier, that you then look up in your database.)

Google API: Authorized JavaScript Origins

I'm implementing a Google+ Sign-In for our web service, and stumbled on "Authorized JavaScript Origins". Our clients have web addresses either as a sub-domain of our main domain, or as a custom domain name. Since the login page is under that sub-domain (or custom domain), and in order to make the Google+ Sing-In button work, that custom domain/sub-domain should be (manually) entered in the "Authorized JavaScript Origins" list (with both http and https).
Does anybody know a way to do that automatically (through some API maybe)?
If not, then how do you do it?
Not sure if there is an API for this. At first glance I don't see one. The alternative (aside from manually adding domains all the time) is to use a hidden iframe on each site - this iframe would come from your domain and would be the only thing that calls google services. The main sites would communicate with the iframe (postMessage) to tell it what to send google. This of course, opens up a security risk (anybody could load your iframe into their page and do bad things on your behalf) so you'll want to make sure that the iframe code refuses to do anything unless it's running within a page on a known-good domain.
You can also have a common URL which all subdomains point to when trying to log in with Google. Then have this URL redirect to your actual Google login path. Beats having to deal with an iframe this way.
Finally I made it to work, however there may be some fixes to apply.
So a server is host for many domain and subdomains (childs) which all of them needs google sign-in and there is a main domain (parent).
I implemented a general login page on parent which childs open this page via window.open() as popup. As client is in a popup, it is very likely that auth2 cannot open another popup, so the parent will do the google auth with {ux_mode: 'redirect'} parameter as gapi.auth2.SignInOptions.
Process will continue to your callback page which you provided as another gapi.auth2.SignInOptions parameter which is redirect_uri and is on parent.
On this page google may have provided you the golden id_token which you must authenticate this token on your server. And this was the main twist which you should use this information to create a token on your server which parent asked server to create, but send it to child on client side (for example via query parameter) to use it for later usage.
I will happily take any advice for security leaks or any comment which may ease the process just a little.

JS Cross-Domain Child to Parent window communication

I'm working on creating an API for my site similar to Facebook Connect. I have a working popup window that works if it is on the same domain as the parent window.
What will be happening though, is users will incorporate my API by including a JS file from my server. When they call the login window, it'll open from their site and allow the user to login on my domain. However, when the user actually logs in, I need to send an event notification to the parent window! As most of you probably know, this cannot happen because these windows are cross domain.
I'm wondering what other current solutions are available for this task. I know if I set the document.domain to be the same on both sites (ie, change the API client's document.domain to my domain - if that's possible?) then the windows can communicate, but I'm not sure that would be at all safe. If the domains can communicate then presumably the API client could wait for the user to open the login window, then read the user's login credentials as they type them in. Is that how it works? Or will they only communicate in one direction?
I'm hoping for a rather simple solution.. But am willing to try out something more complex if need be. I know Facebook is doing it so I know it's possible. Thanks in advance guys.
A quick and dirty hack is to send notifications via the window.name property.

Cookies Between Sites

I'm setting up a site whose entire purpose is essentially a landing page. This page will create a cookie when the user fills out the proper form. To handle cookies I'm using this jquery plugin.
My problem is, I have a separate site that should only be able to be viewed if the user has the cookie from the first site (the landing page). So far, in my testing, I have been having trouble since the cookie that I set at my landing page doesn't appear on the other site. The landing page is being tested on localhost, but the site that requires the user to have the cookie before viewing is live on the internet.
Here is how I set the cookie:
$('#submit')[0].addEventListener("click", $.cookie("test-cookie", "test-value"));
Then, at the other site I have something like this to check the cookie:
var cookie = $.cookie("test-cookie");
if (cookie != null && cookie != "") {
console.log("TRUE");
} else {
window.location = "http://www.thelandingpagesite.com";
}
Now, I'm not sure if the problem is with cookies (I don't know if they can be so easily transfered between sites, as far as I am aware of, they exist on the Users computer), or if I'm just setting it up wrong. Any help would be greatly appreciated! Thanks.
As far as I am aware, one site (www.example.com) cannot retrieve cookies from a browser for another site (www.Second-example.com).
It would be a major security breach if this was allowed as it would be very easy for someone to steal your cookie and gain access to your accounts and personal details.
I am afraid you are going to have to use some mechanism other than cookies.
You could store their IP as you suggest in a a comment on another answer. Just be aware that anyone on that Lan could access the page... for example if a student in a school fills out your form... the whole school would have access to the page you are trying to restrict.
Cookies are stored by the user's browser, but they are stored with a reference to the site that set them.
Site A cannot set a cookie for Site B.
Cookies are used to (among other things) store preferences. Allowing any arbitrary site to set a user's preferences for any other arbitrary site would invite vandalism.
Cookies are set for a specific domain or set of subdomains. They can be readable across multiple subdomains, so it would be possible to set a cookie for domain '.domain.com' that would be sent along with all requests to 'www.domain.com', 'landingpage.domain.com', etc.
If ultimately you would be having your landing page and the page they are be sent to on the same root domain, this would be possible.
It doesn't seem like authenticating a user is an issue with your question, you merely want the user to visit Site A before they can visit Site B.
This question might be of some use:
Cross domain iframe content load detection
On Site B you could have an Iframe pointing to a page on Site A which in turn loads an Iframe pointing to a page on Site B. If the Iframe pointing to Site A doesn't have the cookie, it could pass that information to the Iframe of Site B (by loading a different page perhaps), which when loaded could then call parent.parent.cookieNotSet() (or whatever you decide to call that function) so that Site B would redirect to Site A.
I hope that makes sense. It's a big workaround, but required to get around cross-domain issues. All of this would obviously require that JavaScript is enabled on the browser but what browser doesn't have JavaScript enabled nowadays?

Categories