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.
Related
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.
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.
I have an application that includes an iframe to a page on my website, but i don't want anyone not using the application to access it, so i put basic http authentication on the page, is there a way to make javascript send the information from the application to load the frame so the user doesn't have to know the login details?
EDIT:
Understand that the javascript is obfuscated, which would deter anyone but the most determined from obtaining the login information, in the rare case that happens, i can change the password and publish a new version.
Further information:
I had originally planned to have a user-agent based filter but changing the user-agent of an iframe with IE7 is impossible.
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.
According to the Facebook API documentation, most of the work is handled through javascript.
That means that all the processing is done, and then the front end checks if the user is connected to Facebook/authorized. right?
My question is:
Suppose a user goes to my site for the first time ever.
He clicks on "facebook connect". The javascript verifies him as authentic, and it "redirects" to another page on my server. From then on, how do I know that the user is actually authenticated to my website, since everything is done on frontend?
I think this is correct, but aren't there some security issues..:
-After user clicks Login, Facebook redirects to a page on my site. AND they also create a cookie with a specific "Facebook ID" that is retrieved only from this user. My backened will "read" the cookie and grab that ID...and then associate it to my userID.
If that is correct...then it doesn't make sense. What if people steal other people's "facebook ID" and then forge the cookie? And then my backend sees the cookie and thinks it's the real user...?
Am I confused? If I am confused, please help me re-organize and tell me how it's like.
Facebook Connect uses a clever (or insane, depending on your point of view) hack to achieve cross-site communication between your site and Facebook's authentication system from within the browser.
The way it works is as follows:
Your site includes a very simple static HTML file, known as the cross-domain communications channel. This file is called xd_receiver.htm in the FB docs, but it can be named anything you like.
Your site's login page includes a reference to the Javascript library hosted on Facebook's server.
When a user logs in via the "Connect" button, it calls a function in Facebook's JS API which pops up a login dialog. This login box has an invisible iframe in which the cross-domain communications file is loaded.
The user fills out the form and submits it, posting the form to Facebook.
Facebook checks the login. If it's successful, it communicates this to your site. Here's where that cross-domain stuff comes in:
Because of cross-domain security policies, Facebook's login window can not inspect the DOM tree for documents hosted on your server. But the login window can update the src element of any iframe within it, and this is used to communicate with the cross-domain communications file hosted on your page.
When the cross-domain communications file receives a communication indicating that the login was successful, it uses Javascript to set some cookies containing the user's ID and session. Since this file lives on your server, those cookies have your domain and your backend can receive them.
Any further communication in Facebook's direction can be accomplished by inserting another nested iframe in the other iframe -- this second-level iframe lives on Facebook's server instead of yours.
The cookies are secure (in theory) because the data is signed with the secret key that Facebook generated for you when you signed up for the developer program. The JS library uses your public key (the "API key") to validate the cookies.
Theoretically, Facebook's Javascript library handles this all automatically once you've set everything up. In practice, I've found it doesn't always work exactly smoothly.
For a more detailed explanation of the mechanics of cross-domain communication using iframes, see this article from MSDN.
Please someone correct me if I'm wrong - as I am also trying to figure all this stuff out myself. My understanding with the security of the cookies is that there is also a cookie which is a special signature cookie. This cookie is created by combining the data of the other cookies, adding your application secret that only you and FB know, and the result MD5-Hashed. You can then test this hash server-side, which could not easily be duplicated by a hacker, to make sure the data can be trusted as coming from FB.
A more charming explaination can be found here - scroll about halfway down the page.
Same issues here, and I think Scott is closer to the solution.
Also Im using "http://developers.facebook.com/docs/?u=facebook.jslib-alpha.FB.init" there open source js framework. So things are a little different.
For me, via the opensource js framework, facebook provides and sets a session on my site with a signature. So what I am thinking is to recreate that signature on my side. - if they both match then the user is who he says he is.
So basically if a user wanted to save something to my database, grab the session signature set up by facebook and recreate that signature with php and validate it against the one facebook gave me?
if($_SESSION['facebookSignature'] == reGeneratedSignature){
// save to database
}else{
// go away I don't trust you
}
But how do you regenerate that signature? preferably without making more calls to Facebook?