node.js - 谷歌与 Meteor 的一键式集成
问题描述
我正在将Meteor应用程序与 Google 的One Tap集成。尝试使用 MeteorloginWithGoogle
来让用户保存到Meteor 帐户(内置在 Meteor.js 中)。其复杂性在于
One-Tap 库不是为了授权用户(即产生访问令牌),只是为了验证用户
因此,我必须做的是使用 Google Api 对用户进行身份验证,或者gapi
检索必要的access_token
和id_token
. 这篇文章的道具。
到目前为止,我得到的如下:
HTML
<div data-prompt_parent_id="g_id_onload" style={{ position: "absolute", top: "5em", right: "1em" }} id="g_id_onload"></div>
客户方
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);
});
服务器端
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'];
}
尝试在客户端/服务器上以不同的方式编写此代码,以及考虑解决此问题的方法并仅注册 Meteor 的Accounts.createUser,但这并不理想。[options]
我传递给的有什么问题loginWithGoogle
?我会认为accessToken
并且idToken
足够了......
发生的情况是,在登录时,它确实让我通过 Google One Tap 的第一阶段登录,但后来我投入Meteor.loginWithGoogle
的选项不知何故未被识别:
这有效(一步流程的第一步)=>
但随后它再次要求登录:|
文档说明loginWithGoogle
格式通常为:
Meteor.loginWith<ExternalService>([options], [callback])
关于loginWithGoogle
:
选项还可能包括Google 的附加 URI 参数
Google 的附加 URI 参数
必需:client_id、nonce、response_type、redirect_uri、范围
可选:access_type、display、hd、include_granted_scopes、login_hint、prompt
不幸的是,它显然没有识别出[options]
我正在传递的东西,否则它会将用户保存到 MongoDB,而它没有这样做。
解决方案
好的,找到了答案 - 我正在研究更清洁的东西,但这是当前的修复 - 谢谢jimmy knoot和methodx的一些启发。
注意:其他一切都与上面的原始问题相同。
客户
// this is the callback from the Google One Tap `google.accounts.id.initialize` (see original Stack Overflow question above)
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(tokens, error) {
if (error) {
// An error happened.
console.log(error);
return;
}
//gapi returns tokens including accessToken and idToken...
Meteor.call("createOneTapUser", result, tokens, (error, stampedLoginToken) => {
if (error) {
console.log(error);
}
//this logs in with the token created earlier...should do whatever your normal google login functionality does...
//*********************************
// this is where you skip the Google login popup :)
Meteor.loginWithToken(stampedLoginToken);
});
//*********************************
}
);
});
}
});
};
服务器
createOneTapUser: async (userDetails, accessDetails) => {
//just including details here for what part of a user object would look like from Meteor.loginWithGoogle > note especially resume > loginTokens
let oneTapUserObj = {
services: {
google: {
accessToken: accessDetails.access_token,
idToken: accessDetails.id_token,
scope: ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "openid"], // yours may be different...
expiresAt: accessDetails.expires_at,
id: userDetails.sub,
email: userDetails.email,
verified_email: userDetails.email_verified,
name: userDetails.name,
given_name: userDetails.given_name,
family_name: userDetails.family_name,
picture: userDetails.picture,
locale: "en"
},
resume: {
loginTokens: []
}
} //...whatever your user object normally looks like.
};
//manually inserting the user
Meteor.users.insert(oneTapUserObj);
let newOneTapUser = await Meteor.users.findOne({ "profile.email": userDetails.email });
// generates the login token that goes under user > services > resume > loginTokens...
let stampedLoginToken = Accounts._generateStampedLoginToken();
Accounts._insertLoginToken(newOneTapUser._id, stampedLoginToken);
//sets the social media image from the google account...you'll need to build your own...
userDetails.picture ? scrapeSocialMediaImage(newOneTapUser._id, userDetails.picture) : console.log("Google One Tap user " + newOneTapUser._id + " has no profile picture...");
return stampedLoginToken.token;
}
推荐阅读
- spring-boot - 来自@kafkaListener 的日志主题名称
- javascript - 如何将大文件分块并上传到 Google 存储桶
- node.js - 如果在循环中,为什么广播到特定 ID 将不起作用
- javascript - 当 args 是具有可选 args 的函数时,如何推断函数重载 TS 类型
- sql-server - dbatools(或 SQL Server 的 SMO)- 模式导出期间的表排序
- angular - 对象的访问属性
- javascript - 我的 Phaser 3 游戏仅加载 1 个文本链的字体。我不知道为什么
- oracle - Oracle RECORDS 和记录类型表
- css - CSS图像背景未显示在某些移动页面上
- azure-active-directory - 无法确定 JWT 访问令牌为何不适用于 Azure SQL。连接失败 用户 ' 登录失败
'