You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Node.js自定义认证的Google Actions Builder账号关联问题

Google Actions Builder 自定义OAuth账号关联问题解答

问题描述

我正在创建Google Actions Builder,在账号关联环节有疑问,希望基于自有数据库数据完成账号关联认证。具体需求是:账号关联时传入邮箱,仅当该邮箱处于活跃状态时才允许完成关联,因此编写了自定义Node.js函数。已将关联类型设为OAuth,授权和令牌URL指向自定义函数,但有两个疑问:

  1. 账号关联时如何传递邮箱ID;
  2. 验证邮箱后如何在Google Actions Builder中完成账号关联。

附上我的Node.js代码,希望在/auth回调的if(result)中编写允许关联的逻辑,else中返回提示:

const express = require('express');
const port = 5003;
const app = express();

app.get('/', (req, res) =>{
    res.send(`Welcome to Test App Nodejs`);
})

app.get('/auth', (req, res) =>{        
    var email = req.query.email;
    userModel.findAll({
        where: {
            emailId: email,
            status:1 
        }
    }).then((result) =>{
        if(result){
            // Allow to account link
        } else{
            // to return Thanks for trying to account linking
        }
        
    }).catch((err)=>{
        res.send(err);
    })
    
});

app.listen(port, (req, res)=>{
    console.log(`Test App runing with ${port}`)
})

疑问解答与代码修正

1. 账号关联时传递邮箱ID的方式

由于你使用的是OAuth 2.0授权码流程,Google Actions会引导用户跳转到你配置的授权页面。你需要在这个页面中添加邮箱输入框,让用户主动输入邮箱,同时保留Google传递的OAuth标准参数(client_id、redirect_uri、state等),提交表单时将邮箱与这些参数一起传给后端接口。

2. 验证邮箱后完成账号关联的逻辑

OAuth 2.0流程要求你的服务完成两个核心接口:

  • 授权接口:验证邮箱有效性,通过则生成授权码并跳转回Google的重定向URL;
  • 令牌接口:接收Google传递的授权码,交换生成访问令牌(access token),供后续Actions请求识别用户。

以下是修正后的完整代码:

const express = require('express');
const crypto = require('crypto');
const port = 5003;
const app = express();
app.use(express.urlencoded({ extended: true }));

// 生产环境建议用数据库存储授权码,关联用户信息
const authCodes = new Map();

app.get('/', (req, res) => {
    res.send(`Welcome to Test App Nodejs`);
})

// 授权页面:展示邮箱输入框,保留OAuth参数
app.get('/authorize', (req, res) => {
    const { client_id, redirect_uri, response_type, state } = req.query;
    res.send(`
        <form method="POST" action="/auth">
            <input type="email" name="email" placeholder="输入你的邮箱" required>
            <input type="hidden" name="client_id" value="${client_id}">
            <input type="hidden" name="redirect_uri" value="${redirect_uri}">
            <input type="hidden" name="response_type" value="${response_type}">
            <input type="hidden" name="state" value="${state}">
            <button type="submit">提交</button>
        </form>
    `);
});

// 邮箱验证与授权码生成
app.post('/auth', (req, res) => {        
    const { email, client_id, redirect_uri, state } = req.body;
    userModel.findAll({
        where: {
            emailId: email,
            status: 1 
        }
    }).then((result) => {
        // findAll返回数组,需判断长度确认是否存在有效用户
        if (result.length > 0) {
            // 生成随机授权码(生产环境建议用更安全的算法)
            const authCode = crypto.randomBytes(16).toString('hex');
            // 存储授权码与用户关联信息
            authCodes.set(authCode, { email });
            // 跳转回Google的重定向URL,携带授权码和state参数
            const redirectUrl = new URL(redirect_uri);
            redirectUrl.searchParams.set('code', authCode);
            redirectUrl.searchParams.set('state', state);
            res.redirect(redirectUrl.toString());
        } else {
            // 返回验证失败提示
            res.send('抱歉,该邮箱未激活或不存在,无法完成账号关联');
        }
    }).catch((err) => {
        res.send(`服务器错误:${err.message}`);
    });
});

// 令牌交换接口:Google用授权码换access token
app.post('/token', (req, res) => {
    const { code, client_id, grant_type } = req.body;
    const userInfo = authCodes.get(code);
    if (userInfo && grant_type === 'authorization_code') {
        // 生成访问令牌(生产环境建议用JWT)
        const accessToken = crypto.randomBytes(32).toString('hex');
        // 销毁已使用的授权码
        authCodes.delete(code);
        // 返回符合OAuth标准的响应
        res.json({
            access_token: accessToken,
            token_type: 'Bearer',
            expires_in: 3600 // 令牌有效期1小时
        });
    } else {
        res.status(400).json({ error: 'invalid_grant' });
    }
});

app.listen(port, () => {
    console.log(`Test App running on port ${port}`);
});

关键注意事项

  • 授权页面必须保留Google传递的state参数,用于防止CSRF攻击;
  • findAll返回的是数组,需通过result.length > 0判断是否存在有效用户;
  • 令牌接口需严格遵循OAuth 2.0响应格式,否则Google会拒绝完成账号关联;
  • 生产环境中,授权码和访问令牌的存储与生成需采用更安全的方案(如数据库存储、JWT令牌)。

内容的提问来源于stack exchange,提问作者Ramesh S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 11:54:06