Infinite Redirect Loop on Google One Tap Signin - html

I'm having trouble finding any documentation in regards to Google One Tap UX and how to persist signin state after a signin redirect. I am using the html api, check the code here:
setTimeout(function () {
let target = document.getElementById('google-signin');
target.innerHTML = '<div id="g_id_onload" data-client_id="x" data-context="signin" data-login_uri="https://x/account/google/callback" data-auto_select="true" data-itp_support="true"></div>';
var s = document.createElement("script");
s.src = 'https://accounts.google.com/gsi/client';
document.head.appendChild(s);
console.log('appended script', s);
}, 30000);
</script>
Essentially I am delaying this signin popup for 30 seconds, that part works fine but soon after this is what happens:
Sign in occurs
Redirect happens
Server redirects back to the referer page
After 30 seconds the process starts again
I would have assumed the google sdk would set a cookie or something somewhere but I guess it does not, either that I'm supposed to handle persisting signin state through my own means. I just want to know the correct approach here.
My question is: How does google know if a user has already signed in using Google One Tap UX?

Figured out a solution. Google allows you to put a property on your div tag called data-skip_prompt_cookie="yourcookie" this will skip the one tap prompt if that cookie is present with a truthy value.
What I did was on my server callback in asp.net I added a cookie to the response. This ensures the prompt is only disabled once someone actually signs in.
Response.Cookies.Append(
"yourcookie", "true");
This ensures when my server redirects back to the originating page, the cookie exists and the one tap does not show up again

Related

Tracing redirects with Headless Chrome Developer Tools

I'm using CDP4J, though I expect this question relates directly to Google Chrome DevTools Protocol.
I want to get a list of the HTTP requests made for a webpage and response codes. So that would include the initial request in the main frame and subsequent requests, either made via 3xx redirects or JavaScript-originated navigation.
It's not clear how to do this reliably.
I have tried the following:
Store io.webfolder.cdp.session.Session.getFrameId
Add callback to session with addEventListener, record every event of type io.webfolder.cdp.event.Event.NetworkResponseReceived
Of these, filter those whose frame ID matches.
Of these filter on type io.webfolder.cdp.type.page.ResourceType.Document
I have a URL that I know returns a HTTP 303. But looking at the Events, don't see the original URL, but instead see only the final destination of the redirects. Every single NetworkResponseReceived has a status of 200.
How can I capture the chain of redirects?
I found the answer. The io.webfolder.cdp.event.network.RequestWillBeSent event has getRedirectResponse, which contains a response if it's a redirect.
I've been using the ResponseReceived event for this purpose. This seems to work to get the document URL from the event:
if (session.getTargetId().equals(responseReceived.getFrameId()) && ResourceType.Document.equals(responseReceived.getType())) {
String url = responseReceived.getResponse().getUrl();
...
}

Google Chrome Silent Push Notifications

I've been reading through the docs for Chrome's implementation of the Web Push API here, and I noticed the API says "you promise to show a notification whenever you receive a push" and under limitations it's stated "you have to show a notification when you receive a push message".
After implementing the example on my localhost, I used cURL to send a push notification successfully. I was curious, so I commented out the lines that actually call the showNotification function, and put in a console.log instead and found that I could, in fact, send, receive, and totally ignore a push notification. I even tried using an if-statement to control whether or not to show them based on global boolean that I controlled from my main page, and that worked. So I was wondering if anyone knew what they meant by saying you need to show a notification, and that silent push notifications weren't available?
This wasn't just for the heck of it, I legitimately may need to control whether or not to show these notifications in my web app, so it would be great if this were actually possible. Code below in case you're curious.
self.addEventListener('push', function(event) {
var title = 'New Message';
var body = 'You have received a new message!';
var icon = '/img/favicon.png';
var tag = 'well-notification';
console.log("DID RECEIVE NOTIFICATION")
if(settingsShowNotification) {
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
}
});
EDIT: On Chrome 47, if it's relevant.
UPDATE: After further experimenting, I found the obvious issue that I can't update the original global variable once the user navigates away and then re-navigates to the same page. However, I was able to circumvent this using a variable on the serviceworker itself and sending a message to the service worker using the API described here to toggle the showNotifications boolean.
You do have to show a notification, and if you don't show a notification you get a forced notification from the browser saying "This site has been updated in the background". But the requirements that show the scary message have been relaxed slightly:
As of Jan. '16, it seems like up to the last 10 notifications are checked for whether each showed a notification or not. If one notification in the last ten notifications did not show a notification, that's considered an accident and the browser won't show the scary "This site has been updated in the background". You have to miss two notifications in the last ten for the scary message to appear.
Note: If the URL in the address bar of the active browser tab matches the origin of your page, and the browser is not minimized, you are not required to show a notification. This is probably why your tests succeeded, if you were on the page itself while running your tests.
Chromium bug that tracks the implementation: https://code.google.com/p/chromium/issues/detail?id=437277
Relevant lines of source code: https://code.google.com/p/chromium/codesearch#chromium/src/chrome/browser/push_messaging/push_messaging_notification_manager.cc&l=249

Flash UrlLoader - prevent or suppress authentication dialog box

I have a rssfeed application made in Adobe AIR. It uses the UrlLoader to read the feeds. I need the application to never prompt the user to enter password for a website or accept a certificate of a website if is no longer valid. I prefer to have it fail instead.
I have setup the event listeners for IO+Error and Security_ERROR but that is not enough
Sample urls(this may not work in future)
http://www.dawnanddrew.com/rss2.xml (asks for user and pass atm)
I had another issue with a feed that even if it was a http url the website redirected the loader to https and then the certificate validation failed and the user was asked to accept the certificate. I need this to fail (I mark failed feeds and the user can check them when they want to find the issue and fix them or remove them)
Using AIR (which you are), you can actually tell the application to NOT handle authentication.
var request:URLRequest =new URLRequest("http://www.dawnanddrew.com/rss2.xml");
request.authenticate = false; //default is true, so you need to tell your request to not handle authentication
Then it will fail with an IO error instead of prompting for credentials.
Documentation
Note however, this will only work in AIR and is not a supported property for flash player.
You cannot do it this way. Now, the server returns specific status and the browser reacts to it by showing you a login dialog box. You cannot do this directly in flash and you cannot get default login button. You need to implement this by your own. The perfect way to do so is to check (server side) if the user is logged in and if not - return status ok (200) with an error message (let's say JSON). This specific message would be read by the flash client and a login screen would be shown (custom made). You will also need to implement the login feature by yourself :)
Sorry for the bad news, but this is the way it is with flash.

Access-Control-Allow-Origin error in a chrome extension

I have a chrome extension which monitors the browser in a special way, sending some data to a web-server. In the current configuration this is the localhost. So the content script contains a code like this:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(data)...
xhr.open('GET', url, true);
xhr.send();
where url parameter is 'http://localhost/ctrl?params' (or http://127.0.0.1/ctrl?params - it doesn't matter).
Manifest-file contains all necessary permissions for cross-site requests.
The extension works fine on most sites, but on one site I get the error:
XMLHttpRequest cannot load http://localhost/ctrl?params. Origin http://www.thissite.com is not allowed by Access-Control-Allow-Origin.
I've tried several permissions which are proposed here (*://*/*, http://*/*, and <all_urls>), but no one helped to solve the problem.
So, the question is what can be wrong with this specific site (apparently there may be another sites with similar misbehaviour, and I'd like to know the nature of this), and how to fix the error?
(tl;dr: see two possible workarounds at the end of the answer)
This is the series of events that happens, which leads to the behavior that you see:
http://www.wix.com/ begins to load
It has a <script> tag that asynchronously loads the Facebook Connect script:
(function() {
var e = document.createElement('script');
e.type = 'text/javascript';
e.src = document.location.protocol +
'//connect.facebook.net/en_US/all.js';
e.async = true;
document.getElementById('fb-root').appendChild(e);
}());
Once the HTML (but not resources, including the Facebook Connect script) of the wix.com page loads, the DOMContentLoaded event fires. Since your content script uses "run_at" : "document_end", it gets injected and run at this time.
Your content script runs the following code (as best as I can tell, it wants to do the bulk of its work after the load event fires):
window.onload = function() {
// code that eventually does the cross-origin XMLHttpRequest
};
The Facebook Connect script loads, and it has its own load event handler, which it adds with this snippet:
(function() {
var oldonload=window.onload;
window.onload=function(){
// Run new onload code
if(oldonload) {
if(typeof oldonload=='string') {
eval(oldonload);
} else {
oldonload();
}
}
};
})();
(this is the first key part) Since your script set the onload property, oldonload is your script's load handler.
Eventually, all resources are loaded, and the load event handler fires.
Facebook Connect's load handler is run, which run its own code, and then invokes oldonload. (this is the second key part) Since the page is invoking your load handler, it's not running it in your script's isolated world, but in the page's "main world". Only the script's isolated world has cross-origin XMLHttpRequest access, so the request fails.
To see a simplified test case of this, see this page (which mimics http://www.wix.com), which loads this script (which mimics Facebook Connect). I've also put up simplified versions of the content script and extension manifest.
The fact that your load handler ends up running in the "main world" is most likely a manifestation of Chrome bug 87520 (the bug has security implications, so you might not be able to see it).
There are two ways to work around this:
Instead of using "run_at" : "document_end" and a load event handler, you can use the default running time (document_idle, after the document loads) and just have your code run inline.
Instead of adding your load event handler by setting the window.onload property, use window.addEventListener('load', func). That way your event handler will not be visible to the Facebook Connect, so it'll get run in the content script's isolated world.
The access control origin issue you're seeing is likely manifest in the headers for the response (out of your control), rather than the request (under your control).
Access-Control-Allow-Origin is a policy for CORS, set in the header. Using PHP, for example, you use a set of headers like the following to enable CORS:
header('Access-Control-Allow-Origin: http://blah.com');
header('Access-Control-Allow-Credentials: true' );
header('Access-Control-Allow-Headers: Content-Type, Content-Disposition, attachment');
If sounds like that if the server is setting a specific origin in this header, then your Chrome extension is following the directive to allow cross-domain (POST?) requests from only that domain.

Retaining HTTP POST data when a request is interrupted by a login page

Say a user is browsing a website, and then performs some action which changes the database (let's say they add a comment). When the request to actually add the comment comes in, however, we find we need to force them to login before they can continue.
Assume the login page asks for a username and password, and redirects the user back to the URL they were going to when the login was required. That redirect works find for a URL with only GET parameters, but if the request originally contained some HTTP POST data, that is now lost.
Can anyone recommend a way to handle this scenario when HTTP POST data is involved?
Obviously, if necessary, the login page could dynamically generate a form with all the POST parameters to pass them along (though that seems messy), but even then, I don't know of any way for the login page to redirect the user on to their intended page while keeping the POST data in the request.
Edit : One extra constraint I should have made clear - Imagine we don't know if a login will be required until the user submits their comment. For example, their cookie might have expired between when they loaded the form and actually submitted the comment.
This is one good place where Ajax techniques might be helpful. When the user clicks the submit button, show the login dialog on client side and validate with the server before you actually submit the page.
Another way I can think of is showing or hiding the login controls in a DIV tag dynamically in the main page itself.
You might want to investigate why Django removed this feature before implementing it yourself. It doesn't seem like a Django specific problem, but rather yet another cross site forgery attack.
2 choices:
Write out the messy form from the login page, and JavaScript form.submit() it to the page.
Have the login page itself POST to the requesting page (with the previous values), and have that page's controller perform the login verification. Roll this into whatever logic you already have for detecting the not logged in user (frameworks vary on how they do this). In pseudo-MVC:
CommentController {
void AddComment() {
if (!Request.User.IsAuthenticated && !AuthenticateUser()) {
return;
}
// add comment to database
}
bool AuthenticateUser() {
if (Request.Form["username"] == "") {
// show login page
foreach (Key key in Request.Form) {
// copy form values
ViewData.Form.Add("hidden", key, Request.Form[key]);
}
ViewData.Form.Action = Request.Url;
ShowLoginView();
return false;
} else {
// validate login
return TryLogin(Request.Form["username"], Request.Form["password"]);
}
}
}
Just store all the necessary data from the POST in the session until after the login process is completed. Or have some sort of temp table in the db to store in and then retrieve it. Obviously this is pseudo-code but:
if ( !loggedIn ) {
StorePostInSession();
ShowLoginForm();
}
if ( postIsStored ) {
RetrievePostFromSession();
}
Or something along those lines.
Collect the data on the page they submitted it, and store it in your backend (database?) while they go off through the login sequence, hide a transaction id or similar on the page with the login form. When they're done, return them to the page they asked for by looking it up using the transaction id on the backend, and dump all the data they posted into the form for previewing again, or just run whatever code that page would run.
Note that many systems, eg blogs, get around this by having login fields in the same form as the one for posting comments, if the user needs to be logged in to comment and isn't yet.
I know it says language-agnostic, but why not take advantage of the conventions provided by the server-side language you are using? If it were Java, the data could persist by setting a Request attribute. You would use a controller to process the form, detect the login, and then forward through. If the attributes are set, then just prepopulate the form with that data?
Edit: You could also use a Session as pointed out, but I'm pretty sure if you use a forward in Java back to the login page, that the Request attribute will persist.