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
偶尔能消除这个错误,但会出现其他类似凭证问题。
解决方案
核心问题分析
- 前端暴露AWS密钥:
NEXT_PUBLIC_前缀的变量会被打包到前端代码中,直接暴露AWS密钥属于严重安全风险,绝对不能这么做。 - SSMClient凭证配置错误:你把
IdentityPoolId直接传给了credentials参数,这完全不符合要求——credentials需要的是合法的凭证对象,而非身份池ID。 - 混合使用AWS SDK v2和v3:代码同时用了v2的
AWS.config和v3的SSMClient,容易引发凭证冲突,建议统一使用v3版本。 - 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
相关产品推荐
相关产品推荐

