There is a demand to make cross domain request to a remote service in chrome extension via ajax.
All works properly while user is authorized on a service before he/she uses the extension. But when an unathorized user makes a request extension fails with 401 error code (unathorized).
My sake is to accomplish browser like appearance. If you aren't authorized on some webpage browser dialog-box appears suggesting you to input your credentials. Since they are correct browser display webpage content.
I've tried to add to extensions HTML layout some invisible element that lies on a service to force browser built-in domain authorization dialog appear, but id doesn't seem to be correct approach.
Long story short, do you know some strategy to force browser built-in authorization from chrome extension or simply via javascript code without redirecting user to some separate service webpage. Or, maybe, this is impossible and why?
Code that makes request is simple:
$.ajax({
url: 'service url that requires authorization',
dataType: 'json',
success: function (info){
//success handler
},
error: function (){
//error handler
}
});
WBR, Vitaliy
I think it is possible to listen to chrome.webRequest.onAuthRequired ( https://developer.chrome.com/trunk/extensions/webRequest.html#event-onAuthRequired). In the event listener, you can prompt the user to enter credentials, say, by showing a custom window that looks like the built-in authorization dialog. onAuthRequired is the only event in chrome.webRequest which supports asynchronized blocking, which allows you to fetch credentials (from the user) asynchronously and pass the response (credentials).
chrome.webRequest.onAuthRequired.addListener(function(details, callback){
// Prompt the user to enter credentials. Call
// callback({authCredentials: {username: xxx, password: xxx}});
// when they are ready.
}, {.../*request filter*/}, ['asyncBlocking']);
Related
We are a social start-up and are given a great opportunity by one of the biggest banks in our country. Basically they will feature our website in their Mobile App, and users can access our website via their app in an iFrame. We are currently working on integrating a SSO log in flow between their app and our website, so users are logged in immediately when they open our website in their app.
For this, they have created an API that we're able to use. The API requires 2 main things to get the flow working:
send a pageLoaded() event when the DOM is ready, when this event is sent, we get returned a token to fetch the user's personal information with a public event.
send a pageReady() event when the backend logic processing is done (eg account created and user logged in).
They will show a spinner in their app, until the pageReady event is being sent to them.
My website uses PHP and JS (jQuery) as the main technologies. I am having a number of issues on how to implement this correctly.
This is the code I am using as of now, it works in a certain way, but it's very troublesome for the reasons mentioned below the code snippet:
$(document).ready(function(){
var getUrl = window.location;
var baseUrl = getUrl.protocol + "//" + getUrl.host;
/*** Mobile App JS Code ***/
var receiver = new PostmessageExternalReceiver();
var sender = new PostmessageExternalSender();
receiver.addTokenEventListener(function (api, token, version){
if (api == 'init') {
$.ajax({
type: 'post',
cache: false,
url: baseUrl + '/login/abc/abcLogin',
data: {
'token': token
},
dataType: 'json',
success: function(response){
sender.pageReady();
window.location.replace(response.redirect_url);
}
});
}
});
sender.pageLoaded();
});
These are the problems that i'm not sure how to get around:
Since the sender.pageLoaded(); is in the jQuery document ready, and after performing the AJAX request, we are redirecting again to the same page (the homepage), because after the redirect, the user will be logged in and some extra blocks will show on the homepage. So if we are on the homepage again, the document ready will fire yet another sender.pageLoaded() event, and we are stuck in an infinite redirecting loop.
Right now I am including all 4 API javascript libraries provided by our third party, and my own javascript file including the AJAX request and the pageLoaded() and pageReady(). This is only applicable for users that come to our website via the 3rd party mobile app. For all other visitors in our website this is not applicable and requires extra resources to be loaded in when they're not used. (the ajax request will never be executed for them because a token will not be sent via the mobile app, but still we do not want to send a pageLoaded() for every visitor on our website, even if he is not from the mobile app.
This is a nice-to-have question: since we need to send the pageReady() after our PHP logic (via AJAX request) is done, we cannot do the redirect in PHP, so I return the redirect_url in my AJAX and then do the window.location.replace() method with the redirect URL. In the 3rd party app, this will cause the app to remove the spinner (on the pageReady() event) and show our website, but immediately afterwards we will redirect the user using the window.location.replace() , this causes a refresh of the page, and thus a small annoyance and not so smooth experience for the user. They expect after the spinner is gone that the website is immediately accessible. How would I go around this?
Thanks a lot in advance!
I've made a simple enough owa addin that, when the user launches it, a dialog window is opened (Office.context.ui.displayDialogAsync) where the user can modify certain aspects of the message (affects recipients and mail headers). As far as i can tell, all the async requests complete nearly instantly when the user closes the dialog and the add-in completes its work, but the error is still triggered when attempting to send.
What seems to be the cause is that when the dialog is opened, owa starts spamming requests every second or so to an address that does not exist, and the error occurs until several minutes later the requests stop in what seems like a timeout of sorts.
My developer console is filled with this;
aria-web-telemetry.js:1 POST
https://browser.pipe.aria.microsoft.com/Collector/3.0/?qsp=true&content-
type=application%2Fbond-compact-binary&client-id=NO_AUTH&sdk-version=AWT-Web-
JS-1.1.1&x-apikey=db334b301e7b474db5e0f02f07c51a47-a1b5bc36-1bbe-482f-a64a-
c2d9cb606706-7439&client-time-epoch-millis=1532959882460 404 (Not Found)
So far google has not found me anything helpful, and i just cannot comprehend why Microsoft would be calling home to an address that doesn't exist.
Sure enough, https://appsforoffice.microsoft.com/lib/1/hosted//ariatelemetry/aria-web-telemetry.js contains that url hardcoded. What can i do?
edit 1)
I'm hosting my own trial windows server on a virtual machine, and using the exchange accounts of that server.
The error occurs on all browsers.
Firefox gives me Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://browser.pipe.aria.microsoft.com/Collector/3.0/?qsp=true&content-type=application%2Fbond-compact-binary&client-id=NO_AUTH&sdk-version=AWT-Web-JS-1.1.1&content-encoding=base64&x-apikey=a387cfcf60114a43a7699f9fbb49289e-9bceb9fe-1c06-460f-96c5-6a0b247358bc-7238&client-time-epoch-millis=1532965578192. (Reason: CORS request did not succeed).
IE does not really give me anything i can pick out
The example above was from chrome
I have not installed it on desktop, so only web so far.
As for code.
My manifest defines an action for a button control
<Action xsi:type="ExecuteFunction">
<FunctionName>showMessageSecurityDialog</FunctionName>
</Action>
that function in turn shows the dialog
Office.context.ui.displayDialogAsync(window.location.origin + dialogPage, { height: 50, width: 75, displayInIframe: true }, dialogCallback);
On the dialog, when the user presses the save button it runs
Office.context.ui.messageParent(true); to signal we're done
from here
dialog = asyncResult.value;
dialog.addEventHandler(
Microsoft.Office.WebExtension.EventType.DialogMessageReceived,
messageHandler
);
dialog.addEventHandler(
Microsoft.Office.WebExtension.EventType.DialogEventReceived,
eventHandler
);
is called, which neatly flows into the message handler
dialog.close();
if (arg.message == true) {
applyMessageSecurity();
}
applyMessageSecurity in turn runs a whole lot of async requests, and when the promises of those requests are resolved, i let the user know with Office.context.mailbox.item.notificationMessages.addAsync("information", {type: "informationalMessage", persistent: false, message: "success"})
afaik we should be done at this point. All the code is done running, the dialog is closed, yet, something in the background is still on causing owa to think the addin is running
Please ensure to call event.completed() after dialog closing or where your code finishes all of its work so that outlook can be notified that the current uiless code has been completed. You can find more details in this article.
I am using chrome.identity.launchWebAuthFlow to retrieve an authentication code from my Rails app which is set up as an OAuth2 provider using the Doorkeeper gem (the Doorkeeper side of things is working).
So I send then request to my server with this method from the Chrome extension:
requestGrant: function(){
chrome.identity.launchWebAuthFlow(
{
'url': authService.grantEndPoint(),
'interactive': true
}, function(redirect_url){
/* Extract auth code from redirect_url */
var code = authService.extractCode(redirect_url);
alert(code);
authService.getAccessToken(code);
}); //launchWebAuthFlow ends here
}
And my server receives the request and redirects to
https://<my_chrome_extension_name>.chromiumapp.org/oauth2?code=<access_code_generated_by_oauth>
with a 302 found.
But, the Chrome extension immediately closes, and the callback function of launchWebAuthFlow does not run. I know it is not running because I call alert() in the callback (doesn't run), and make another request to my server for the access token (server does not receive the request).
I think part of the problem is that the chrome extension might be closing right when launchWebAuthFlow is called, because the window launchWebAuthFlow opens is a web view of the servers authorization flow, and the extension closes (or is the auth flow just being displayed through the extension?)
For some reason launchWebAuthFlow's default behavior is to close the window according the documentation, but the callback still isn't running.
How can I get the callback function to run, and prevent the chrome extension window from closing?
I was running the launchWebAuthFlow in non background script. I moved the auth logic to a background script and it works fine.
I too was experiencing almost the same issue. I was trying to launch the webauthflow from my popup.html but the popup.html would close once the auth flow began, aborting the code that would execute upon succesful return of a token.
My suggestion is to instead take care of authentication in options.html.
(https://developer.chrome.com/extensions/optionsV2)
This will launch a popup modal that stays open even after your auth flow opens, (as opposed to popup.html closing when it loses focus), meaning the rest of your code will execute.
Hope this helps.
My application interacts with Google with Javascript only. It asks for user profile access, email access and contacts management permissions.
Upon loading a page, the application checks if the user has already granted those permissions and obtains an access token if he had.
Here is some sample code:
var GoogleContacts = {
...
checkAuth: function(){
gapi.auth.authorize({
client_id: googleKeys.clientId,
scope: googleKeys.scopes,
immediate: true
},
jQuery.proxy(this.handleAuthResult, this)
);
},
askAuth: function(){
gapi.auth.authorize({
client_id: googleKeys.clientId,
scope: googleKeys.scopes,
immediate: false
},
jQuery.proxy(this.handleAuthResult, this)
);
}
...
}
....
function handleGoogleApiLoad(){
gapi.client.setApiKey(googleKeys.apiKey);
gapi.auth.init(function(){console.info('popup api ready')});
setTimeout(function(){GoogleContacts.checkAuth();}, 300);
}
....
$('#emailButton').click(function() {
if(!accessToken)
GoogleContacts.askAuth();
...
});
Now, if user comes for the first time, he is asked the correct permissions when he pressed the "Send email" button. When user reloads a page, the seamless permissions check returns failure and when user hits a "send email" button, we open the Google authorization popup again, and it now asks for Offline Access permission.
This seems incorrect as the JS api has no actual use for offline access.
Looks like this problem started after Google released the incremental auth feature: http://googleplusplatform.blogspot.co.il/2013/12/google-sign-in-improvements11.html
Is this a bug that will soon be fixed, or should we change the code somehow to not confuse our users with weird permission requests?
Update:
I have tried to use the plus api and gapi.auth.signIn() method but with the same result.
Apparently, this problem is scope-dependant, as when I use only the login scope, everything works as expected, but adding the Google Contacts access scope https:||www.google.com/m8/feeds/ always leads to the Offline Access request when entering page second time. Here is a fiddle to confirm this: http://jsfiddle.net/hjLM6/6/
This must be a bug and I really would like Google to deal with it soon, as it scares users away.
The immediate:false parameter in your askAuth method is the cause. The post that Abraham mentions explains the background.
The gapi.auth.authorize() method should generally be avoided in most cases now that the gapi.auth.signIn() method is available to handle programatic initiation of the authorization flow and also you should make use of the dynamic callbacks. The information on monitoring a user's session state explains how and when to get your sign-in callback function to fire and how you can use the values within the auth result object to determine if they've previously authorized your app, signed in (or out) of your app, or signed in (or out) of google.
Your checkAuth and askAuth functions would effectively be combined to check the status of the auth result object and act accordingly. Your email button click event would trigger instead gapi.auth.signIn() with the necessary parameters and scopes for your app.
I just had exactly the same issue with drive.readonly scope, for what it's worth the way I worked around it is by always calling authorize with immediate = false. This isn't that bad because when you do this for an already authorized user, Google will open the popup for a fraction of a second but then will immediately close it (apparently making use of the chance to open the popup in the browser event handler in case the user does need to authorize).
Curiously, for localhost server where I previously used immediate = true, I continue to see requests for offline access - but on the production server I haven't seen seen them so far, fingers crossed.
I've got quite a few GreaseMonkey scripts that I wrote at my work which automatically log me into the internal sites we have here. I've managed to write a script for nearly each one of these sites except for our time sheet application, which uses HTTP authentication.
Is there a way I can use GreaseMonkey to log me into this site automatically?
Edit: I am aware of the store password functionality in browsers, but my scripts go a step further by checking if I'm logged into the site when it loads (by traversing HTML) and then submitting a post to the login page. This removes the step of having to load up the site, entering the login page, entering my credentials, then hitting submit
It is possible to log in using HTTP authentication by setting the "Authorization" HTTP header, with the value of this header set to the string "basic username:password", but with the "username:password" portion of the string Base 64 encoded.
http://frontier.userland.com/stories/storyReader$2159
A bit of researching found that GreaseMonkey has a a function built into it where you can send GET / POST requests to the server called GM_xmlhttpRequest
http://diveintogreasemonkey.org/api/gm_xmlhttprequest.html
So putting it all together (and also getting this JavaScript code to convert strings into base64 I get the following
http://www.webtoolkit.info/javascript-base64.html
var loggedInText = document.getElementById('metanav').firstChild.firstChild.innerHTML;
if (loggedInText != "logged in as jklp") {
var username = 'jklp';
var password = 'jklpPass';
var base64string = Base64.encode(username + ":" + password);
GM_xmlhttpRequest({
method: 'GET',
url: 'http://foo.com/trac/login',
headers: {
'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey/0.3',
'Accept': 'application/atom+xml,application/xml,text/xml',
'Authorization':'Basic ' + base64string,
}
});
}
So when I now visit the site, it traverses the DOM and if I'm not logged in, it automagically logs me in.
HTTP authentication information is sent on every request, not just to log in. The browser will cache the login information for the session after you log in the first time. So, you don't really save anything by trying to check if you are already logged in.
You could also forget about greasemonkey altogether and just give your login into on the url like so:
http://username:password#host/
Of course, saving this in a bookmark may be a security risk, but not more-so than saving your password in the browser.
Why don't you use Firefox (I assume you're using Firefox) to remember your credentials using the Password Manager?
I found this link: HTTP Authentication with HTML Forms. Looks like you can use javascript to do HTTP authentication. I don't think you can have Greasemonkey interrupt when you are first navigating to a URL though. You might have to setup some sort of launching point that you can use to have greasemonkey automatically redirect + login. For example, you can have the local page that takes the destination URL in the query string, have Greasemonkey automatically do the authenticate + redirect. The only problem is that you'll have to wrap the site bookmarks with your launching page for the bookmarks you use as entry points.
"http://username:password#host/" doesn't work on IE, FireFox works ok.