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

NextJS+Amplify接入第三方API时遇凭证缺失等错误求助

问题描述

我清楚部分日志存在风险,仅在本地代码中保留,未提交到版本库。
被凭证相关错误困扰多日,查遍资料仍无解。目前正在尝试将第三方API接入NextJS Amplify应用,持续遇到Credential is missing类错误,怀疑和const client = new SSMClient({...})的配置有关,但无法确定具体问题点。反复检查AWS凭证配置步骤,没发现遗漏。

相关代码

.env.local

(已在Amplify中存储相同变量值,不确定是否必要;BrevoAPI密钥已按AWS文档存在SSM中,不确定是否需要Cognito信息)

NEXT_PUBLIC_aws_access_key_id=[redacted]
NEXT_PUBLIC_aws_secret_access_key=[redacted]
NEXT_PUBLIC_brevoapi=[redacted]
NEXT_PUBLIC_cognitopool=[redacted]
NEXT_PUBLIC_cognitoclient=[redacted]

GetSecret.js

import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm" 

export default async function GetSecret() {

var AWS = require("aws-sdk");

AWS.config.update({
    accessKeyId: process.env.NEXT_PUBLIC_aws_access_key_id,
    secretAccessKey: process.env.NEXT_PUBLIC_aws_secret_access_key,
    region: 'us-east-2',
    credentials: new AWS.CognitoIdentityCredentials({
        IdentityPoolId: process.env.NEXT_PUBLIC_cognitopool,
    })
})

const client = new SSMClient({
    region: AWS.config.region,
    credentials: AWS.config.credentials.IdentityPoolId
})

const input = {
    Name: "BREVOAPI",
    WithDecryption: true,
}

console.log("client is: " + JSON.stringify({client}))

const command = new GetParameterCommand(input)
const response = await client.send(command)
// var request = await ssm.getParameter(input).promise();
console.log("request is " + response)
return response
// const response = await client.send(command)   
}

JS-Form.jsx

import GetSecret from './GetSecret'

export default function JSForm() {

// Handles the submit event on form submit.

const handleSubmit = async (event) => {
  // Stop the form from submitting and refreshing the page.
  event.preventDefault()

  // Get data from the form.
  const secret = await GetSecret()
  console.log("secret is: " + secret)
  const fname = event.target.fname.value
  const lname = event.target.lname.value
  const email = event.target.email.value

  // API endpoint where we send form data.
  const endpoint = 'https://api.brevo.com/v3/contacts'

  // Form the request for sending data to the server.
  const options = {
    // The method is POST because we are sending data.
    method: 'POST',
    // Tell the server we're sending JSON.
    headers: {
        accept: 'application/json',
        'content-type': 'application/json',
        'api-key': secret
      },
    // Body of the request is the JSON data we created above
    body: JSON.stringify({
      attributes: {FIRSTNAME: fname, LASTNAME: lname},
      listIds: [8],
      updateEnabled: false,
      email: email
    })
  }

  // Send the form data to Brevo API and get a response.
  const response = await fetch(endpoint, options)
  const errorCode = res.ok ? false : res.status
  .then(response => response.json())
  .then(response => console.log(response))
  .catch(err => console.error(err, errorCode));

  // Get the response data from server as JSON.
  // If server returns the name submitted, that means the form works.
  // const result = await response.json()
}

return (
  <div className="hero">
  <form onSubmit={handleSubmit}>
    <div className="mx-auto">
      <div className="mt-10">
        <label className="label" htmlFor="fname">
          <span className="label-text-lg text-white">What is your first name? (optional)</span>
        </label>
        <input type="text" id="fname" placeholder="First Name (optional)" className="input input-bordered w-full max-w-xs placeholder-white" />
      </div>
      <div className="mt-10">
        <label className="label" htmlFor="lname">
          <span className="label-text-lg text-white">What is your last name? (optional)</span>
        </label>
        <input type="text" id="lname" placeholder="Last Name (optional)" className="input input-bordered w-full max-w-xs placeholder-white" />
      </div>
      <div className="mt-10">
        <label className="label">
          <span className="label-text-lg  text-white" htmlFor="emailAddress">What is your email?</span>
        </label>
        <input type="email" id="email" placeholder="Cthuhlu@PraiseBeUntoCthuhlu.com" className="input input-bordered w-full max-w-xs placeholder-white" required/>
      </div>
      <div className="text-white prose text-left flex-grow mt-10">
        ...
      </div>
      <div className="text-white prose mt-10">
        ...
      </div>
      <div>
        <button className="btn mt-10 mb-10 text-white font-bold bg-base-250" type="submit" rel="noopener noreferrer">
          Sign Up
        </button>
      </div>
    </div>
  </form>
  </div>
)
}
补充错误信息

Unhandled Runtime Error
Error: Credential is missing

偶尔能消除这个错误,但会出现其他类似凭证问题。

解决方案

核心问题分析

  1. 前端暴露AWS密钥:NEXT_PUBLIC_前缀的变量会被打包到前端代码中,直接暴露AWS密钥属于严重安全风险,绝对不能这么做。
  2. SSMClient凭证配置错误:你把IdentityPoolId直接传给了credentials参数,这完全不符合要求——credentials需要的是合法的凭证对象,而非身份池ID。
  3. 混合使用AWS SDK v2和v3:代码同时用了v2的AWS.config和v3的SSMClient,容易引发凭证冲突,建议统一使用v3版本。
  4. Amplify凭证管理错误:Amplify部署和本地开发时,会自动处理身份凭证,不需要手动硬编码AWS密钥。

修复步骤

1. 迁移SSM调用到Next.js API路由(后端执行)

创建pages/api/get-brevo-secret.js,所有AWS服务调用放在后端,避免前端接触凭证:

import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";

export default async function handler(req, res) {
  if (req.method !== 'GET') {
    return res.status(405).json({ message: '仅支持GET请求' });
  }

  try {
    // 本地开发时Amplify CLI自动配置凭证,部署后Amplify自动注入角色权限
    const client = new SSMClient({ region: 'us-east-2' });
    
    const input = {
      Name: "BREVOAPI",
      WithDecryption: true,
    };

    const command = new GetParameterCommand(input);
    const response = await client.send(command);
    
    return res.status(200).json({ apiKey: response.Parameter.Value });
  } catch (error) {
    console.error('获取密钥失败:', error);
    return res.status(500).json({ message: '获取密钥失败' });
  }
}

2. 修改GetSecret.js为调用API路由

export default async function GetSecret() {
  const response = await fetch('/api/get-brevo-secret');
  if (!response.ok) {
    throw new Error('获取密钥失败');
  }
  const data = await response.json();
  return data.apiKey;
}

3. 清理.env.local中的敏感信息

删除暴露AWS密钥的变量,保留必要的公开变量:

NEXT_PUBLIC_cognitopool=[redacted]
NEXT_PUBLIC_cognitoclient=[redacted]

4. 修正JS-Form.jsx中的fetch错误

原代码中未定义res变量,替换为response:

// 发送数据到Brevo API
const response = await fetch(endpoint, options);
const errorCode = response.ok ? false : response.status;
response.json()
  .then(data => console.log(data))
  .catch(err => console.error(err, errorCode));

5. 配置Amplify角色权限

确保Amplify部署使用的IAM角色拥有ssm:GetParameter权限,在IAM控制台给角色添加如下策略:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ssm:GetParameter",
      "Resource": "arn:aws:ssm:us-east-2:你的AWS账号ID:parameter/BREVOAPI"
    }
  ]
}

注意事项

  • 永远不要在前端代码中存储AWS密钥、API密钥等敏感信息。
  • 本地开发时,确保已通过Amplify CLI初始化项目,Amplify会自动处理本地凭证。
  • SSM参数需设置为安全字符串(SecureString),确保密钥加密存储。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 15:57:27