2

I've been researching this issue for quite some time now and have had no success. Hoping someone can shed some light on this!

I'm working on an Google Chrome Extension (browser action) where authentication/authorization is being done outside of Google (chrome.identity.launchwebauthflow with interactive set to true). We've had success with the authentication/authorization flow for some users but not all. Here are the interesting results:

  1. User A clicks extension icon, clicks Authorize button, successfully gets auth code, exchanges it for access tokens, and can proceed with using the application.
  2. User B clicks extension icon, clicks Authorize button, extension pop up closes. It fails prior to exchanging auth code for access tokens.
  3. User B right clicks on extension icon, selects Inspect popup, clicks Authorize button, successfully gets auth code, exchanges it for access tokens, and can proceed with using the application.
  4. User A starts up device in Safe mode with networking. User A clicks extension icon, clicks Authorize button, extension pop up closes. It fails prior to exchanging auth code for access tokens.
  5. User A starts up device in Safe mode with networking. User A opens up a tab and loads the extension's url (chrome-extension://pathofextension). User clicks Authorize button, successfully gets auth code, exchanges it for access tokens, and can proceed with using the application.
  6. Extension is converted to a packaged app. User A and B opens app, clicks Authorize button, successfully gets auth code, exchanges it for access tokens, and can proceed with using the application.

We think it's a client issue, but we're all using the same Chrome versions. What would cause the extension's pop up window to close when the auth code is being returned? We can't keep the developer console open to see if any errors appear because when the developer console is open it works just fine. We are using $.ajaxSetup({ cache:false }) to make sure caching is disabled for ajax requests.

Here's a snippet of the chrome.identity.launchwebauthflow call (originally called from the popup):

chrome.identity.launchWebAuthFlow({url:url,interactive:true},function(response) {
    console.log('OAuth Response: '+response);
    if (response) {
        var authCode = encodeURIComponent(response.substring(response.indexOf('=')+1));
        console.log('Auth Code: '+authCode);
        userAuthorization(authCode);
    } else {
        authorizeButton();
    }
});

Edited code after trying to apply the code-in-background solution:

Pop up script now calls background script:

chrome.runtime.sendMessage({type:'authorize',url:url},function(response) {
    console.log(chrome.runtime.lastError);
    console.log(response);
    if (response && response.authCode) {
        userAuthorization(response.authCode);
    } else {
        authorizeButton();
    }
});

Background script responds to pop up script.

chrome.runtime.onMessage.addListener(function(message,sender,sendResponse) {

    if (message.type == 'authorize') {

        var url = message.url,
            authCode;

    chrome.identity.launchWebAuthFlow({url:url,interactive:true},function(response) {
            console.log('OAuth Response: '+response);
            if (response) {
                authCode = encodeURIComponent(response.substring(response.indexOf('=')+1));
                console.log('Auth Code: '+authCode);
            }
            sendResponse({authCode:authCode});
        }); 

    }

    return true;

});
Xan
  • 74,770
  • 16
  • 179
  • 206
jacorre
  • 23
  • 6
  • Where is `launchWebAuthFlow` called from? Popup code? – Xan Mar 01 '16 at 00:57
  • launchwebauthflow is called from within the javascript of the pop up window (when you click the extension's icon and the pop up window appears). – jacorre Mar 01 '16 at 16:16

1 Answers1

5

Invoking launchWebAuthFlow from an extension popup is a very bad idea.

This operation is supposed to create a new window and focus on it. By Chrome UI conventions, this should close the extension popup - and with it, completely destroy the JavaScript context of that page. There will no longer be any callback to call.

This explains why "Inspect popup" helps - this prevents closing the popup on focus loss. There is no override for this mechanism outside of this debugging case.

This popup-dismissal behavior may subtly differ by OS, hence you might not have seen it on your development machine. But the convention is clear - any loss of focus should destroy the popup page.

The only truly persistent part of your extension that cannot be accidentally closed is the background script - that's where you should handle the chrome.identity authorization. Send a message from your popup code that requests it.

Update: Note that you can't return a response to sendMessage for the same reason - the popup no longer exists. Your logic should be to try to retrieve the token with interactive: false every time the popup opens - and if that fails, request the background to initiate the interactive flow (and expect to be closed, so no sendResponse).

Community
  • 1
  • 1
Xan
  • 74,770
  • 16
  • 179
  • 206
  • Users testing are using the same OS, but I appreciate the feedback on doing the call from the background script. I'll give that a try. Thanks! – jacorre Mar 01 '16 at 17:12
  • It can be a race condition as well - how fast the JS context is destroyed after the popup closes. – Xan Mar 01 '16 at 17:16
  • For users that it didn't work for, they are seeing the following error: Error in response to identity.launchWebAuthFlow: Error: Attempting to use a disconnected port object Sounds like a timing thing? I am using a background script where the launchwebauthflow is being called and I did not define the persistent flag. – jacorre Mar 01 '16 at 18:34
  • Well, you can't `sendResponse` for the very same reason: the popup is closed and does not exist anymore. Nothing to send it to. – Xan Mar 01 '16 at 19:06
  • For some users it works just fine though? The pop up isn't closed when the launchwebauthflow opens the web view. The web view is closed and the pop up is updated with what it should show when you're now authenticated and authorized. – jacorre Mar 01 '16 at 19:52
  • That shouldn't happen by design of the popup. Maybe you should submit this inconsistency as a bug to https://crbug.com/new - and you can try to accomodate both by sending a message (not replying with sendResponse): this will not raise an error if nobody is listening. – Xan Mar 01 '16 at 19:55
  • I send message from pop up to background script. Background script does authentication flow and stores tokens, but now I'm stuck with the pop up window staying open. I can't programmatically close it from the background script. At least I haven't found a way yet. – jacorre Mar 01 '16 at 20:02
  • You can programmatically close it from the popup itself with `window.close()`. – Xan Mar 01 '16 at 20:02
  • Unfortunately, the user experience with closing the pop up as soon as I send a message to the background script isn't the best. It may look as if is something didn't work if the auth flow window doesn't open for a few seconds. Though even if I could close it from the background the user would still have to click the extension icon again after authorization. Appreciate the help working through this. – jacorre Mar 01 '16 at 20:21
  • There's no way around it since the popup sometimes force-closes. From UX perspective, maybe it's best to check the token non-interactively on extension start, and if that fails replace the `browserAction` popup with an `onClicked` event that either launches the interactive flow or shows a tab explaining this. – Xan Mar 01 '16 at 20:24
  • When clicking the extension icon, I check for an access token and if not present, I show the authorize button. I agree with you that maybe I should instead open a tab explaining the authorization process and in that page start the authorization process. When that's done, click the extension icon again will make more sense. Thanks again! – jacorre Mar 01 '16 at 20:36
  • You can use `browserAction.setPopup({popup:""})` to programmatically prevent the popup from opening if you detected (before the click happened) that interactive login will be required (and do it upon `onClicked`). Note that `onClicked` and a popup set are mutually exclusive with priority going to a popup. – Xan Mar 01 '16 at 20:38