Retrieving email with Google Sign In for Google Home - actions-on-google

so I've beentrying to retrieve the email associated with the current user using the google home.
Documentation is kind of hard to find on the subject and from what I could gather, I should be able to use the SignIn class from actions-on-google. So here is my setup.
DialogFlow -> Created two intent, one to start the sign in process, the other to follow up on the process. (The second one has the event 'actions_intent_SIGN_IN' to it.)
Actions on google config : Account Linking.
Selected - Yes, allow users to sign up for new accounts via voice
Selected - Linking Type : Google Sign In
And added the client id to my fulfillment layer by adding the clientId to my dialogflow config.
dialogflow({clientId})
So, when I run this in the emulator (I get the same exact thing on my google home device) I get an error as soon as my sign in goes to the followup intent (actions_intent_SIGN_IN), which is that my signin.status is Error. From there, I don't know what I can do to get more information on what this error is and how to fix it.
Any idea ? Thanks !
PS : It might not even be something that can be done ? Is there any other way to retrieve the email of the user ? I was able to retrieve it's name using Permission, but there's nothing more that SignIn for email as far as I know.

I can show you how I get my email address with the Google Account linking:
You need to have your accessToken available then you could use what is shown in this answer. The accessToken is in conv.user.access.token when the SignIn is completed.
In node this looks like that:
let link = "https://www.googleapis.com/oauth2/v1/userinfo?access_token="+accessToken;
return new Promise(resolve => {
request(link,(error, response, body) => {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
let name = data.given_name ? data.given_name : '';
conv.ask(new SimpleResponse({
speech: "Hello "+ name + "!",
text: "Hello "+ name + "!"
}));
resolve();
} else {
console.log("Error in request promise: "+error);
resolve();
}
})
})
Everything you need should be in the data object.
Hope it helps.
About your error, I am not sure but try doing all step in the link above. I'm using the Sign-In required box with Dialogflow and not a new SignIn but it should work the same.

Related

Flutter oAuth : how to get started with OAuth and Stripe connect

I am trying to implement stripe connect in my flutter app. Here are the steps I need to implement. Can anyone please navigate me on how I could achieve this in Flutter?
I am able to create a button with the endpointUrl but that's all..
Thanks
I found out this myself using firebase cloud functions:
first you create an https function in the firebase cloud function
then you add the link created by the function to your stripe dashboard
then you write the following logic to your function
obtain the the authorisation code
fetch data from stripe
save the response somewhere (in my case in realtime database)
Here is the function
exports.connectStripeStandardAccount = functions.https.onRequest((req, res) => {
let authCode = req.query.code;
return stripe.oauth.token({
grant_type: 'authorization_code',
code: authCode,
}).then(async response => {
await admin.database()
.ref(`/accounts/${authCode}`)
.set(response);
return res.send("Well done, account integration is completed. You can now close the window and go back to the app");
});
});
The answer selected is not completely correct:
If you dont assign the account_id to a user then it's of no use.
The only way to pass the user_id (fUser.uid) is to pass it using the state parameter.
exports.StripePI = functions.https.onRequest(async (req, res) => {
// console.log('accountIdq ' + req.query.error);
// console.log('accountIdq ' + req.query.state);
// return;
// if(!req.query.code)
// return res.send("An Error has occured please try again");
const response = await stripe.oauth.token({
grant_type: 'authorization_code',
code: req.query.code,
}).then(async response => {
var connected_account_id = response.stripe_user_id;
await admin.firestore().collection('Registration').doc(req.query.state)
.update({customer_id : connected_account_id});
return res.send("Well done, account integration is completed. You can now close the window and go back to the app");
});
});
If you want to create an in-app stripe connect account registration with flutter you will need these:
A server or service to complete the OAuth like Firebase Functions or Integromat (I used Integromat)
A link that will redirect to your app (I used Firebase Dynamic Link)
STEPS TO CREATE THE REGISTRATION FLOW
INTEGROMAT/FIREBASE FUNCTIONS SETUP
I decided to use Integromat instead of Firebase Functions because is easier to set up, doesn't need any code, and decreases my server load.
If you want to create it on Firebase Functions you will need to have a Blaze Plan
If you don't know it, Integromat will automate processes that you currently handle manually, via webhooks. It is not only capable of connecting apps (like GoogleCloud, Facebook, AWS...) but can also transfer and transform data.
Create a new scenario and add a Custom Webhook. Click on it and click on add, name it, and save it. It will now create a custom link to your webhook.
Close and click on the semi-sphere next to the webhook, to add the new module.
Select HTTP and Make a Request.
In the URL section insert https://connect.stripe.com/oauth/token.
Method POST.
Body Type Application/x-www-form-urlencoded.
Create now those fields :
Key client_secret - value your stripe client secret You can find it on your stripe dashboard. I advise you to first use the test mode and after that, change the value to the live key.
Key grant_type - value authorization_code
Key code - leave the value blank. We will add it later.
Save and close
For Firebase Functions you can create a new HTTPS function (I didn't test this)
var stripe = require("stripe")(*your stripe client secret*);
exports.connectStripeStandardAccount = functions.https.onRequest((req, res) =>{
let authCode = req.query.code;
return stripe.oauth.token({
grant_type: 'authorization_code',
code: authCode,
});
});
Remember to install stripe package npm install stripe
STRIPE SETUP
If you are in the test mode go to this link
If you are in the live mode go to this link
Go on the bottom and activate oAuth for standard accounts or for Express Account.
Click on Add URI and add the webhook link of Integromat that you created or the link related to your Firebase function.
If you used Firebase add this link https://us-central1-<project-id>.cloudfunctions.net/connectStripeStandardAccount
For Integromat you will need to create the structure. To do this click on Test OAuth, copy the link, and open it in incognito mode. Open your Integromat scenario and click on your webhook. Now click on Re-determine data structure.
Return to your stripe registration page and click on Ignore account form at the top.
Return on Integromat and select the HTTPS request, modify the field code, and insert the variable code (will open a dialog with all queries from the webhook). Confirm and save.
Now click on the play button and reopen the stripe registration link in incognito mode and click on Ignore account form. Return in Integromat and add a JSON module after the HTTPS request. In the JSON string insert the Data variable and save. Create a Webhook Response module after the JSON module.
In the status put 301, then click on Ok.
DEEP LINK SETUP
It's time to set up the redirect link that will return the user to our flutter app or on our website if the user hasn't it installed.
I used Firebase Dynamic Link You can follow this tutorial for set up.
Go to the dashboard and create a new Link prefix and a new dynamic link, remember to select to redirect your users to the right app.
Click on the three dots in your dynamic link row and click on Link Details. Copy the extended link.
Open Integromat and select the last module you created (Webhook Response). Click on Show advanced settings and on the Header add :
Key Location - value the extended dynamic link that you copied.
If you want your app to elaborate data from the stripe OAuth response you can modify the extended dynamic link by adding ? on the link parameter: link=https://test.page.link?stripe_user_id={{14.stripe_user_id}}
And select the variable parsed from the JSON module. Remember to click on the save icon to save your scenario.
On Firebase Functions you can do this when the function stripe.oauth.token finish (I didn't test it):
res.setHeader('Location', your dynamic link);
res.status(301).send();
Remember to deploy it.
FLUTTER APP SETUP
The code here is very simple. To initialize the connect account registration you only need to set up a button that will launch the stripe connect URL. You can use launch(url);
You can find that URL here. Remember to be logged in to your stripe account to get the right stripe client id. You can easily get it in the same section you added the webhook link in your stripe connect settings.
Delete &redirect_uri=https://sub2.example.com on the URL.
Now you can test your app and will see that when you complete your stripe connect registration/login you will be redirected to your app.
If you want to have an in-app web view you can use this package
To handle the response, you need to have installed the package firebase_dynamic_links
Set your Main widget Stateful and on the initState run the method getDynamic() :
void getDynamic() {
FirebaseDynamicLinks.instance.getInitialLink().then((value) {
if (value != null) {
_connect(value);
}
});
FirebaseDynamicLinks.instance.onLink(onSuccess: (value) async {
if (value != null) {
_connect(value);
}
}, onError: (error) async {
debugPrint('DynamicLinks onError $error');
});
}
void _connect(value) {
Uri deepLink = value.link;
print("Link :" + deepLink.path);
print("Query :" + deepLink.queryParameters.toString());
String stripeUserId = deepLink.queryParameters["stripe_user_id"];
}
You need to have both of them to handle dynamic links when your app is running and when it's closed.

Getting a Voice Match error when trying to call Actions on Google SignIn helper

What I'm trying to achieve is user authentication (account linking) before the default welcome intent is called.
(I'm aware that in the design documents it is recommended to not require users to authenticate, but it is required for our action)
We have an undeployed action that I have tested in the simulator. The conversation flow is
The user is prompted to signin ie, new SignIn()
They are prompted to create an new item
The item is saved and the user receives a success/fail response and continues
In DialogFlow I have a Default Welcome Intent (prompts the user to create an new item), an intent to capture the item.
Our fulfillment intents
app.intent('ask_for_sign_in_detail', (conv) => {
conv.ask(new SignIn());
});
app.intent('ask_for_sign_in_confirmation', (conv, params, signin) => {
if (signin.status !== 'OK') {
return conv.ask('You need to sign in before using the app.');
}
// const access = conv.user.access.token;
// exchange access.token for jwt from backend
return conv.ask('Great! Thanks for signing in.');
});
app.intent('Default Welcome Intent - fallback', (conv) => {
createItem(conv);
});
In DialogFlow under integrations for the Google Assistant I have required that Sign In is required before the Default Welcome Intent.
When I go to test in the simulator I can see that under account linking there is no information (ie, no accounts linked) and there is a message stating 'No account is linked to Google. Start a test conversation for account linking.'
Starting a conversation I get the following error:
Your voice wasn't recognized, so I can't connect you to Talkatoo.
Check the Voice Match settings in the Google Home app.
I am not sure where I have strayed trying to authenticate users with their Google accounts.
This has been fixed by Google as of 2019-07-19 02:57 PDT. Received a response from the AoG team, and tested it in the simulator. It was a Google bug.

Actions on Google implicit account linking works in simulator/browser, but not on device (via Google Home app)

I've implemented the implicit flow for Actions on Google account linking, and am using Dialogflow (previously API.AI) to define intents.
The full flow works in the device simulator (from AOG). The first intent gets a "It looks like your account isn't linked yet..." response, and the debug pane includes a URL to initiate linking:
https://assistant.google.com/services/auth/handoffs/auth/start?account_name=[account]#gmail.com&provider=[project_id]_dev&scopes=email&return_url=https://www.google.com/
If I follow this URI in a cache-less window:
I'm redirected to my app's authentication page
I choose to sign in with my Google account (same as [account] above)
I'm redirected to google.com with a success message in the URI bar
The simulator now accepts actions via my app and responds correctly
However, if I follow the same flow using a physical Google Home & the gH app for Android.
Device tells me account not yet linked
Open Google home and follow 'Link to [my app]' link
Browser opens to authentication page
Sign in as user
Redirected to a white page with a single link "Return to app", which has an href: about:invalid#zClosurez
Linking was unsuccessful, so additional attempts to run intents on the Google Home get the same "Account not yet linked" response.
I've inspected the intermediate access_token and state variables at length, and they all match and look to be correctly formatted:
Authentication URL (app sign in page): https://flowdash.co/auth/google?response_type=token&client_id=[client_id]&redirect_uri=https://oauth-redirect.googleusercontent.com/r/[project_id]&scope=email&state=[state]
After authenticating, redirected to (this is the white screen with 'return to app' broken link): https://oauth-redirect.googleusercontent.com/r/genzai-app#access_token=[token]&token_type=bearer&state=[state]
So, it seems there's something non-parallel about the way the simulator and physical devices work in terms of implicit flow account linking.
I've been struggling with this, and with the AOG support team for a very long time to no avail. Anyone else see a similar issue?
Updated with response redirect code:
Login handled by react-google-login component with profile & email scopes. On success we call:
finish_auth(id_token) {
let provider = {
uri: '/api/auth/google_auth',
params: ['client_id', 'redirect_uri', 'state', 'response_type'],
name: "Google Assistant"
}
if (provider) {
let data = {};
provider.params.forEach((p) => {
data[p] = this.props.location.query[p];
});
if (id_token) data.id_token = id_token;
api.post(provider.uri, data, (res) => {
if (res.redirect) window.location = res.redirect;
else if (res.error) toastr.error(res.error);
});
} else {
toastr.error("Provider not found");
}
}
provider.uri hits this API endpoint:
def google_auth(self):
client_id = self.request.get('client_id')
redirect_uri = self.request.get('redirect_uri')
state = self.request.get('state')
id_token = self.request.get('id_token')
redir_url = user = None
if client_id == DF_CLIENT_ID:
# Part of Google Home / API.AI auth flow
if redirect_uri == "https://oauth-redirect.googleusercontent.com/r/%s" % secrets.GOOGLE_PROJECT_ID:
if not user:
ok, _email, name = self.validate_google_id_token(id_token)
if ok:
user = User.GetByEmail(_email, create_if_missing=True, name=name)
if user:
access_token = user.aes_access_token(client_id=DF_CLIENT_ID)
redir_url = 'https://oauth-redirect.googleusercontent.com/r/%s#' % secrets.GOOGLE_PROJECT_ID
redir_url += urllib.urlencode({
'access_token': access_token,
'token_type': 'bearer',
'state': state
})
self.success = True
else:
self.message = "Malformed"
else:
self.message = "Malformed"
self.set_response({'redirect': redir_url}, debug=True)
I am able to make it work after a long time. We have to enable the webhook first and we can see how to enable the webhook in the dialog flow fulfillment docs If we are going to use Google Assistant, then we have to enable the Google Assistant Integration in the integrations first. Then follow the steps mentioned below for the Account Linking in actions on google:-
Go to google cloud console -> APIsand Services -> Credentials -> OAuth 2.0 client IDs -> Web client -> Note the client ID, client secret from there -> Download JSON - from json note down the project id, auth_uri, token_uri -> Authorised Redirect URIs -> White list our app's URL -> in this URL fixed part is https://oauth-redirect.googleusercontent.com/r/ and append the project id in the URL -> Save the changes
Actions on Google -> Account linking setup 1. Grant type = Authorisation code 2. Client info 1. Fill up client id,client secrtet, auth_uri, token_uri 2. Enter the auth uri as https://www.googleapis.com/auth and token_uri as https://www.googleapis.com/token 3. Save and run 4. It will show an error while running on the google assistant, but dont worry 5. Come back to the account linking section in the assistant settings and enter auth_uri as https://accounts.google.com/o/oauth2/auth and token_uri as https://accounts.google.com/o/oauth2/token 6. Put the scopes as https://www.googleapis.com/auth/userinfo.profile and https://www.googleapis.com/auth/userinfo.email and weare good to go. 7. Save the changes.
In the hosting server(heroku)logs, we can see the access token value and through access token, we can get the details regarding the email address.
Append the access token to this link "https://www.googleapis.com/oauth2/v1/userinfo?access_token=" and we can get the required details in the resulting json page.
`accessToken = req.get("originalRequest").get("data").get("user").get("accessToken")
r = requests.get(link)
print("Email Id= " + r.json()["email"])
print("Name= " + r.json()["name"])`
Not sure which python middleware or modules you are using but
self.set_response({'redirect': redir_url}, debug=True)
seems to be setting parameters for a returning a response which isn't correct. Instead you should redirect your response to the redirect_url. For example importing the redirect module in Flask or Django like:
from flask import redirect or from django.shortcuts import redirect
then redirect like:
return redirect(redirect_url)
It appears Google has made a change that has partially solved this problem in that it is now possible to complete the implicit account linking flow outside of the simulator, in the way outlined in my question.
It seems the problem stemmed from an odd handling (on the AOG side) of the client-side redirect case used after sign in with the Google sign-in button.
From Jeff Craig in this thread:
The current workaround, where we provide the "Return to app" link
currently what we're able to provide. The issue is with the way that
redirecting to custom-scheme URIs is handled in Chrome, specifically,
with regard to the redirect happening in the context of a user action.
XHR will break that context, so what is happening is that you click
the Google Sign-In Button, which triggers an XHR to Google's servers,
and then you (most likely) do a client-side redirect back to the
redirect_url we supply, our handler executes, and isn't able to do a
JS redirect to the custom scheme URI of the app, because were outside
of the context of a direct user click.
This is more of a problem with the Implicit (response_type=token) flow
than with the authorization code (response_type=code) flow, and the
"Return to app" link is the best fallback case we currently have,
though we are always looking for better solutions here as well.
The current behavior shows the 'Return to app' link, but as of last week, this link's href is no longer about:invalid#zClosurez, but instead successfully completes the sign-in and linking process. It's an odd and confusing UX that I hope Google will improve in the future, but it was sufficient to get my app approved by the AOG team without any changes to my flow.

Swift2 Firebase: Is the email check done on the backend server? [duplicate]

Question says it all. In Firebase, how do I confirm email when a user creates an account, or, for that matter, do password reset via email.
I could ask more broadly: is there any way to send emails out from Firebase? E.g. notifications, etc. This isn't the kind of thing you would usually do client-side.
Update
Note that this was never a very secure way of handling email verification, and since Firebase now supports email verification, it should probably be used instead.
Original answer
I solved the email verification using the password reset feature.
On account creation I give the user a temporary (randomly generated) password. I then trigger a password reset which will send an email to the user with a link. The link will allow the user to set a new password.
To generate a random password you can use code similar to this:
function () {
var possibleChars = ['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?_-'];
var password = '';
for(var i = 0; i < 16; i += 1) {
password += possibleChars[Math.floor(Math.random() * possibleChars.length)];
}
return password;
}
Note that this is happening on the client, so a malicious user could tamper with your logic.
This would need to be done outside of firebase. I store users at /users/ and keep a status on them (PENDING, ACTIVE, DELETED). I have a small service that monitors users of a PENDING status and sends out a confirmation email. Which has a link to a webservice I've created to update the user status to ACTIVE.
[Engineer at Firebase - Update 2014-01-27]
Firebase Simple Login now supports password resets for email / password authentication.
Each of the Simple Login client libraries has been given a new method for generating password reset emails for the specified email address - sendPasswordResetEmail() on the Web and Android, and sendPasswordResetForEmail() on iOS.
This e-mail will contain a temporary token that the user may use to log into their account and update their credentials. This token will expire after 24 hours or when the user changes their password, whichever occurs first.
Also note that Firebase Simple Login enables full configuration of the email template as well as the sending address (including whitelabel email from your domain for paid accounts).
To get access to this feature, you'll need to update your client library to a version of v1.2.0 or greater. To grab the latest version, check out https://www.firebase.com/docs/downloads.html.
Also, check out https://www.firebase.com/docs/security/simple-login-email-password.html for the latest Firebase Simple Login - Web Client docs.
As at 2016 July, you might not have to use the reset link etc. Just use the sendEmailVerification() and applyActionCode functions:
In short, below is basically how you'll approach this, in AngularJS:
// thecontroller.js
$scope.sendVerifyEmail = function() {
console.log('Email sent, whaaaaam!');
currentAuth.sendEmailVerification();
}
// where currentAuth came from something like this:
// routerconfig
....
templateUrl: 'bla.html',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn() // this throws an AUTH_REQUIRED broadcast
}]
}
...
// intercept the broadcast like so if you want:
....
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
if (error === "AUTH_REQUIRED") {
$state.go('login', { toWhere: toState });
}
});
....
// So user receives the email. How do you process the `oobCode` that returns?
// You may do something like this:
// catch the url with its mode and oobCode
.state('emailVerify', {
url: '/verify-email?mode&oobCode',
templateUrl: 'auth/verify-email.html',
controller: 'emailVerifyController',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn()
}]
}
})
// Then digest like so where each term is what they sound like:
.controller('emailVerifyController', ['$scope', '$stateParams', 'currentAuth', 'DatabaseRef',
function($scope, $stateParams, currentAuth, DatabaseRef) {
console.log(currentAuth);
$scope.doVerify = function() {
firebase.auth()
.applyActionCode($stateParams.oobCode)
.then(function(data) {
// change emailVerified for logged in User
console.log('Verification happened');
})
.catch(function(error) {
$scope.error = error.message;
console.log(error.message, error.reason)
})
};
}
])
And ooh, with the above approach, I do not think there's any need keeping the verification of your user's email in your user data area. The applyActionCode changes the emailVerified to true from false.
Email verification is important when users sign in with the local account. However, for many social authentications, the incoming emailVerified will be true already.
Explained more in the article Email Verification with Firebase 3.0 SDK
What I did to work around this was use Zapier which has a built in API for firebase. It checks a location for added child elements. Then it takes the mail address and a verification url from the data of new nodes and sends them forwards. The url points back to my angular app, which sets the user email as verified.
As I host my app files in firebase, I don't need have to take care of any servers or processes doing polling in the background.
There is a delay, but as I don't block users before verifying mails it's ok. Zapier has a free tier and since I don't have much traffic it's a decent workaround for time being.
The new Firebase SDK v3 appears to support email address verification, see here (put your own project id in the link) but it doesn't appear to be documented yet.
I have asked the question on SO here
See #SamQuayle's answer there with this link to the official docs.
As noted by various others Firebase does now support account related emails but even better, as of 10 days ago or so it also supports sending any kind of email via Firebase Functions. Lots of details in the docs and example code here.
I used following code to check the email verification after creating new account.
let firAuth = FIRAuth.auth()
firAuth?.addAuthStateDidChangeListener { auth, user in
if let loggedUser = user {
if loggedUser.emailVerified == false {
loggedUser.sendEmailVerificationWithCompletion({ (error) in
print("error:\(error)")
})
}
else {
print(loggedUser.email)
}
} else {
// No user is signed in.
print("No user is signed in.")
}
}
I used MandrillApp. You can create an API key that only allows sending of a template. This way even thought your key is exposed it can't really be abused unless someone wants to fire off tonnes of welcome emails for you.
That was a hack to get myself off the ground. I'm now enabling CORS from a EC2 that uses the token to verify that the user exists before extending them a welcome via SES.

Google Sign-In with Passportjs not getting authenticated

I'm using Sails with Passport for authentication. I'm using passport-google-oauth(OAuth2Strategy) and passport-facebook for enabling Google Sign-in.
I'm not too well-versed with Passport, so pardon me if this is a rookie question. I've set up login via Facebook and it works just fine. With Google, I do receive an authorization code after allowing access to the app, but the I'm eventually not authenticated. I'm guessing the same code should work for both Facebook and Google since the strategies are both based on oauth2.
I'm not even sure what code to share, since I'm using the auto-generated code from sails-generate-auth, but do let me know if there's anything else I can share.
Any ideas on why this might be happening? The app is locally hosted but that's unlikely to be the problem since I am getting to the authorization stage anyway.
I faced the same problem and it was located here in in api/services/passport.js:
// If the profile object contains a list of emails, grab the first one and
// add it to the user.
if (profile.hasOwnProperty('emails')) {
user.email = profile.emails[0].value;
}
// If the profile object contains a username, add it to the user.
if (profile.hasOwnProperty('username')) {
user.username = profile.username;
}
// If neither an email or a username was available in the profile, we don't
// have a way of identifying the user in the future. Throw an error and let
// whoever's next in the line take care of it.
if (!user.username && !user.email) {
return next(new Error('Neither a username nor email was available'));
}
The Google service was not returning a profile.username property.
Because of it, the user is not saved in the database and cannot be authenticated. Then the passport callback receives an empty user, so the function that handles errors is fired and the user is redirected to the login page.
This change allows to use the displayName property as the username:
// If the profile object contains a list of emails, grab the first one and
// add it to the user.
if (profile.hasOwnProperty('emails')) {
user.email = profile.emails[0].value;
}
// If the profile object contains a username, add it to the user.
if (profile.hasOwnProperty('username')) {
user.username = profile.username;
}
/** Content not generated BEGIN */
// If the username property was empty and the profile object
// contains a property "displayName", add it to the user.
if (!user.username && profile.hasOwnProperty('displayName')) {
console.log(profile); // <= Use it to check the content given by Google about the user
user.username = profile.displayName;
}
/** Content not generated END */
// If neither an email or a username was available in the profile, we don't
// have a way of identifying the user in the future. Throw an error and let
// whoever's next in the line take care of it.
if (!user.username && !user.email) {
return next(new Error('Neither a username nor email was available'));
}
You could also use the profile.id property because profile.displayName is not necessarily unique (ie: two Google accounts can have an identical displayName). But it is also true accross different services: a Twitter account could also have the same username than a Facebook account. If both register on your application, you will have a bug. This is a problem from the code generated by sails-generate-auth and you should adapt it with the behavior that you want.
I will propose a PR if this solution works for you too.
Alright, so this ultimately turned out to be a known issue with the API.
TL;DR: Enable the Google+ API and the Contacts API as mentioned here. (The Contacts API isn't required, as #AlexisN-o pointed out in the comments. My setup worked as desired with Contacts API disabled. This obviously depends on what scope you're using.)
I believe it's not a nice way of failing since this was an API error that was prevented from bubbling up. Anyway, I dug into passport.authenticate to figure out what was going wrong. This eventually calls the authenticate method defined in the package corresponding to the strategy (oauth2 in this case). In here (passport-google-oauth/lib/passport-google-oauth/oauth2.js) I found that the accessToken was indeed being fetched from Google, so things should be working. This indicated that there was a problem with the requests being made to the token urls. So I ventured a little further into passport-oauth2/lib/strategy.js and finally managed to log this error:
{ [InternalOAuthError: failed to fetch user profile]
name: 'InternalOAuthError',
message: 'failed to fetch user profile',
oauthError:
{ statusCode: 403,
data: '{
"error": {
"errors": [{
"domain": "usageLimits",
"reason": "accessNotConfigured",
"message": "Access Not Configured. The API (Google+ API) is not enabled for your project. Please use the Google Developers Console to update your configuration.",
"extendedHelp": "https://console.developers.google.com"
}],
"code": 403,
"message": "Access Not Configured. The API (Google+ API) is not enabled for your project. Please use the Google Developers Console to update your configuration."
}
}'
} }
This was the end of the hunt for me and the first result for the error search led to the correct answer. Weird fix though.