This is a great question. The Meteor loginwithgoogle is limited…I am integrating a Meteor application with Google’s One Tap. Attempting to use Meteor’s loginWithGoogle
in order to get the user to save to Meteor Accounts. However,
One-Tap library is not meant to authorize the user (i.e. produce Access Token), only to authenticate the user
Thus, what I’ve had to do is authenticate the user using Google Api, or gapi
to retrieve the necessary access_token
and id_token
.
What I’ve got so far is as follows:
HTML
<div data-prompt_parent_id="g_id_onload" style={{ position: "absolute", top: "5em", right: "1em" }} id="g_id_onload"></div>
CLENT SIDE
google.accounts.id.initialize({
prompt_parent_id: "g_id_onload",
client_id: "42424242-example42.apps.googleusercontent.com",
auto_select: false,
callback: handleCredentialResponse
});
const handleCredentialResponse = async oneTapResponse => {
// see the SERVER SIDE code, which is where validation of One Tap response happens
Meteor.call("verifyOneTap", oneTapResponse.credential, oneTapResponse.clientId, (error, result) => {
if (error) {
console.log(error);
}
if (result) {
// Initialize the JavaScript client library.
gapi.load("auth2", function() {
// Ready. Make a call to gapi.auth2.init or some other API
gapi.auth2.authorize(
{
client_id: oneTapResponse.clientId,
scope: "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
response_type: "code token id_token",
prompt: "none",
// this is the actual email address of user, example@gmail.com, passed back from the server where we validated the One Tap event...
login_hint: result.email
},
function(result, error) {
if (error) {
// An error happened.
console.log(error);
return;
}
//these are the authentication tokens taht are so difficult to capture...
let theAccessToken = result.access_token;
let theIdToken = result.id_token;
//*********************************
//this is the part that doesn't work
//trying to get it to create the account without another Google prompt...
Meteor.loginWithGoogle({ accessToken: theAccessToken, idToken: theIdToken, prompt: "none" }, function(err, res) {
if (err) {
console.log(err)
}
});
//*********************************
}
);
});
}
});
};
google.accounts.id.prompt(notification => {
//this just tells you when things go wrong...
console.log(notification);
});
SERVER SIDE
const { OAuth2Client } = require("google-auth-library");
const clientOA2 = new OAuth2Client("42424242-example42.apps.googleusercontent.com");
// the token and clientId are returned from One Tap in an object, are credential (token) and clientId (clientId)
verifyOneTap: async (token, clientId) => {
const ticket = await clientOA2.verifyIdToken({
idToken: token,
audience: clientId // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = await ticket.getPayload();
//perform validation here so you don't get hacked...
return payload;
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
Tried writing this in different ways on the client/server, as well as considered ways to go around this and just signing up with Meteor’s Accounts.createUser
, but it is less than ideal. What is wrong with the [options]
that I am passing to loginWithGoogle
? I would think accessToken
and idToken
were enough…
What happens is that on login, it does log me in through the first stage of Google One Tap, but then options that I threw into Meteor.loginWithGoogle
are somehow not being recognized:
first step of One Step flow works => but then it asks for login again
The documentation on loginWithGoogle
states that the format is typically:
Meteor.loginWith<ExternalService>([options], [callback])
and with regards to loginWithGoogle
:
options may also include Google’s additional URI parameters
Google’s Additional URI Parameters
Required : client_id, nonce, response_type, redirect_uri, scope
Optional : access_type, display, hd, include_granted_scopes, login_hint, prompt
Unfortunately, it is clearly not recognizing something in the [options]
that I am passing, otherwise it would save the user to MongoDB, which it isn’t doing.