Google Oauth popup cancellation callback - javascript

When using Google Identity Services (GSI) I can display a popup to ask users to connect with their Google account.
This is pretty well documented and it works well with this code:
const client = window.google.accounts.oauth2.initCodeClient({
client_id: 'CLIENT_ID',
scope: 'SCOPE',
ux_mode: 'popup',
callback: async (response) => {
console.log('Response Google', response);
},
});
client.requestCode();
However, I wish to do something if the user close the popup. I can't find anything in the documentation and in examples online. I tried intermediate_iframe_close_callback and native_callback, but neither are called when closing the popup.
So, is it possible ? How can I do it ?
Thanks

I think the callback name is "error_callback". You can find details at: Handle Errors
const client = google.accounts.oauth2.initCodeClient({
client_id: 'YOUR_GOOGLE_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
ux_mode: 'popup',
callback: myCallback,
error_callback: myErrorCallback // You can do something when popup window closed
});

It appears that this is not working for the current version of GSI.
It did work for the old gapi version and if the popup were to be closed you would get a response with the error: {error: "popup_closed_by_user"}. As referenced in this answer: Google SSO login error: "popup_closed_by_user"
Hopefully adding the #google-oauth tag will allow someone at Google to see this and hopefully update this script.

Related

How to detect the "closed_by_user" event via the new "Google 3P Authorization JavaScript Library API"

I'm currently using the Google Sign-In JavaScript Platform Library for web.
Via the grantOfflineAccess() function I request access to scopes from the users Google account with ux_mode: 'popup'.
auth2.grantOfflineAccess().then(
response => {
// response handling
},
error => {
// error handling
},
)
Currently I'm able to detect if the popup is closed without authorization, because the error callback is called with this data:
{
error: "popup_closed_by_user"
}
As Google is discontinuing this Sign-In API I'm migrating to the new Google 3P Authorization JavaScript Library
As part of the migration, I have modified the authorization flow to code like this:
google.accounts.oauth2.initCodeClient({
client_id: '[...].apps.googleusercontent.com',
fetch_basic_profile: false,
ux_mode: 'popup',
scope: '[...]',
callback: response => {
// handle google feedback
},
}).requestCode()
As the docs state, the callback can have the parameters code, scope, state, error, error_description, error_uri and I expected it to be the equivalent of the previous callbacks, but it does not seem to be called for the popup_closed_by_user event.
Unfortunately, I was also not able to find an alternative callback.
Is someone aware wether the popup_closed_by_user event can be detected with the new library?

How can I test that the user can link OAuth providers using Cypress?

I'm working on a project in which a user can log in using whether a phone number, using Google, or using Github. I use Firebase for authentication. I'm currently adding integration tests using Cypress. My problem is I can't properly test that the user can link or unlink a provider. To link a provider I use linkWithRedirect() firebase method.
At first, I tried to just simulate a button click. But this didn't work because Cypress doesn't allow visiting multiple domains in one test.
My next attempt was to use linkWithCredential() method. I get cookies and the provider's auth URL from the developer tools to get authorization code and use this code to get access token.
My Cypress command to get Google token:
Cypress.Commands.add(
'getGoogleToken',
() => {
cy.request({
url: Cypress.env("GOOGLE_AUTH_URL"),
headers: {
cookie: Cypress.env("GOOGLE_AUTH_COOKIE")
},
followRedirect: false
})
.its("redirectedToUrl")
.then(redirectedToUrl => {
const url = new URL(redirectedToUrl);
const code = url.searchParams.get("code");
cy.request({
url: "https://www.googleapis.com/oauth2/v4/token",
method: "POST",
form: true,
body: {
code,
redirect_uri: Cypress.env("GOOGLE_REDIRECT_URI"),
client_id: Cypress.env("GOOGLE_CLIENT_ID"),
client_secret: Cypress.env("GOOGLE_CLIENT_SECRET"),
grant_type: "authorization_code"
}
})
.its("body")
.then(body => body.access_token);
});
});
Using this token I call my own linkWithGoogleWithToken method which calls linkWithCredential method:
Cypress.Commands.add(
'linkWithGoogle',
() => {
cy.getGoogleToken()
.then(token => {
cy.get("#auth")
.invoke("linkWithGoogleWithToken", token);
});
});
In my tests, I call several functions to link one provider, then link another provider, then unlink provider, and so on. Sometimes this command works as expected, but sometimes it remains in the pending state forever. I can't find why. Searching doesn't help, it seems like no one has encountered this problem.
So, I am looking for ways to link OAuth provider programmatically, not using UI. I'm considering Firebase Admin SDK, but I can't find the way to link providers in its documentation. I would appreciate any help from anyone who was encountering this issue.
I found out why I was having this problem. The point is that after visiting the page, I add an alias to refer to my firebase functions:
Cypress.Commands.add(
'init',
() => {
cy.visit("/");
cy.window()
.its("firebase")
.its("auth").as("auth");
cy.nullifyUsername();
});
The first time when I called the invoke method, everything worked fine. Then I reloaded the page because I need to update the page with this information.
cy.linkGoogle();
cy.reload();
cy.get("#googleButton")
.contains(/^unlink$/i);
And after that, the next time I tried to link another provider the invoke method remained in the pending state. That happened because after reloading the page, my alias was referred to a function that was no longer exists. My solution is to add alias after reloading the page. So, I created custom command for this:
Cypress.Commands.add(
'refresh',
() => {
cy.reload();
cy.window()
.its("firebase")
.its("auth").as("auth");
});

Login using facebook get username, email

I am using login using facebook in my website,
Here is the button
<fb:login-button scope="public_profile,email" onlogin="checkLoginState();">
</fb:login-button>
I am using the exact example given here
But after the check login status, i am getting the response with only the userid of facebook and token, but i want to get the username too.. How can i get that ?
Help pls
Here is the script
window.fbAsyncInit = function () {
FB.init({
appId: 'xxxx',
cookie: true, // enable cookies to allow the server to access
// the session
xfbml: true, // parse social plugins on this page
version: 'v2.2' // use version 2.2
});
FB.getLoginStatus(function (response) {
console.log(response);
console.log(response.email);
//here status is connected
//statusChangeCallback(response);
});
};
In the resopnse, i am getting the entire json,
In the response.status i am getting as connected
But while i try response.email i am getting undefined
How can i get the email or username ?
EDITED to show what actually fixed it for me:
So it does appear to depend on what version of the API your config is set to allow. BUT, I was able to get the email address on a v2.5 api by using the following:
If you are using the Hello World code, then replace this section:
FB.api('/me', function (response) {
console.log('Success ');
console.log(response);
});
With this:
FB.api('/me', { fields: 'email' }, function (response) {
console.log('Success ');
console.log(response);
});
The difference is adding the { fields: 'email' } object to the call. This appears to have been a new addition in either v2.4 or v2.5 of the API which is why older code you see on SO appears to work for the poster but not for you. Hope this helps.
I'm not sure this qualifies as a solution, but I was able to reproduce this issue (no email returned after a valid request).
I have two apps, each created under a different FB account. The first one was created more than a year ago and the second one was created today. I have code that runs perfectly against the older app (returns email) and fails against the newer app (no email).
At first I thought maybe FB just holds back on certain data for brand new apps but I thought it was odd that it wouldnt be documented anywhere.
So I decided to compare each app configuration side by side. I found that the older one had API VERSION (at the top of the dashboard) set to 2.0 and the newer was 2.5. The Hello World code on the API SDK page requests version 2.2 like so:
window.fbAsyncInit = function () {
FB.init({
appId: [my app id here],
cookie: true, // enable cookies to allow the server to access
// the session
xfbml: true, // parse social plugins on this page
version: 'v2.2'
})
};
According to the documentation, the fact that the newer app has API set to 2.5 in the config means that any requests for an older API (eg 2.2) will be automatically upgraded to 2.5. When I set the javascript above to request 2.5 (eg version: 'v2.5'), I was able to get the older app to fail too. So I suspect the issue is with the newer v2.5 api.
I have not been able to find out how to set my new app to accept older api calls but will update here if I do.

Chrome Extension with G+ Signin - redirect_uri_mismatch

I'm trying to create a Chrome Extension that the user can login to with their Google account. I have based it on this sample code: https://developers.google.com/+/quickstart/javascript and it gets as far as gapi.auth2.init but then 400 errors with Error: redirect_uri_mismatch. The client ID is registered fine.
The API call fails because the redirect_uri is wrong, but what URI do I set for an extension? The API call is made as so:
gapi.auth2.init({
fetch_basic_profile: false,
client_id: '1234_valid_id_1234',
cookie_policy: 'none',
redirect_uri: 'xxx',
scope:'https://www.googleapis.com/auth/plus.login'
}).then(
'xxx' as the uri is just an example, I've tried multitude of options in here, to no avail.
The extension will load a dialogue as if it's going to begin the auth process but then craps out with the 400 error. I don't understand what URI an extension should be providing as a callback here, surely this isn't applicable?
The client ID etc is fine - if I run the sample JS code on just a standard localhost website it all works fine, it's only when trying to use it in an extension that it breaks, because of the lack of callback URI.
https://developers.google.com/identity/sign-in/web/reference
gapi.auth2.init doesn't take redirect_uri param.
Only function that takes redirect_uri is grantOfflineAccess. redirect_uri = "postmessage" should always work.
var auth2 = gapi.auth2.init({
fetch_basic_profile: false,
client_id: '1234_valid_id_1234',
cookie_policy: 'none',
scope:'https://www.googleapis.com/auth/plus.login'
});
// on user click
auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(
function(resp) {
var auth_code = resp.code;
});

Google Javascript API (gapi) - problems with .load

I am trying to use the Google plus API (via googie-api-javascript) implementation like so (omitting full code):
var clientId = '7454475891XxxxxxXom4c6n.apps.googleusercontent.com'; //fake client
var apiKey = '-uTH_p6NokbrXXXXXXXXXXXXX'; //Fake Key
var scopes = 'https://www.googleapis.com/auth/plus.me';
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
makeApiCall();
} else {
//handle user-approval
}
}
// Load the API and make an API call. Display the results on the screen.
function makeApiCall() {
gapi.client.load('plus', 'v1', function() {
var o = gapi.client.plus;
alert(o);
});
}
The code works well upto the point of gapi.client.load (including the user allowing access) - this callback gets called but alert(o) will return undefined.
Upon inspecting the HTTP request I see the .load issues a request to:
https://content.googleapis.com/discovery/v1/apis/plus/v1/rpc?fields=methods%2F*%2Fid&pp=0&key=-uTH_p6NokbrXXXXXXXX
This returns HTTP 400 with the following message:
{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}
My question is - what do I need to change to make this work?
Is there some secret setting I need to enable ? Google+ is enabled in the google-developer-console under the APIs list.
Thanks for the help,
Alon
Problem:
.load issues a request to the google discovery service to load the .JS. The service will error out if the request it receives contains an api-key. (I don't know why the library works like this, it seems like a bug?)
Fix:
gapi.client.setApiKey(""); //NEW
gapi.client.load('plus', 'v1', function()
//re-add the key later if you need it
From Discovery Service docs:
requests you make to the Discovery Service API should not include an API key. If you do provide a key, the requests will fail.
Weird... :P
A little update & more of an explanation. The current Discovery Service page is a little more specific now. They indicate that if the app has an Oauth2 token, then the API Key value is not required. However, I also found that if I have an authenticated user and thus an Oauth2 token (access_token) present, the Discovery Service fails with the error noted in the OP. This seems to contradict the documentation.
You can see the token in the developer tools console with:
console.log(gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse());
Embed that somewhere in a <script>...</script>in your HTML or in a .js file that is called otherwise. Must be after gapi.load(...). It'll stop the script if executed before gapi.load(...) is called.
To get a current user this has to be after the user is authenticated, of course. It does return an object if a user has not been authenticated however. If you are in Chrome, you can expand The Object in the developer tools console window to get a nice outline format of all the stuff in the auth response.
Note that currentUser is undefined prior to a successful authentication. Since this 'fails silently' you should use a conditional statement to verify either the sign in status or that a current user exists in your real code.
For completeness the object instantiation process in my app goes like this, at a high level:
1.) gapi.load(...) - After this gapi.client, gapi.auth2 and other objects are available.
2.) gapi.client.setApiKey("") would be called to 'clear' the api key if it had been set previously for some other purpose.
3.) gapi.auth2.init(...) - After this the auth instance is available via gapi.auth2.getAuthInstance .
4.) Then the login is kicked off using the .signIn() method of the auth instance. The auth instance would be instantiated with something like auth_instance = gapi.auth2.getAuthInstance(); If that's how you do it then the sign in would be auth_instance.signIn().
(...) - means there are several parameters needed.
I also found the Google tictactoe example useful as an example and a simple base for further work.
I hope this is helpful to someone!
you need to call the method
function handleAuthClick(event) {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false} handleAuthResult);
return false;
}
function makeApiCall() {
gapi.client.load('plus', 'v1', function () {
var request = gapi.client.plus.people.get({
'userId': 'me'
});
request.execute(function (resp) {
'method ajax with you application'
});
});
}
you can see what this do here

Categories

Resources