AWS Amplify Auth+Cognito的JWT id_token缺失nonce/at_hash问题求助
解决Amplify Authenticator隐式流中id_token缺失at_hash和nonce的问题
针对你想用Amplify Auth实现无缝SSO隐式流,却遇到id_token缺少at_hash和nonce的问题,我来给你详细的解决方案——这两个需求都是可以实现的:
一、让id_token包含at_hash声明
at_hash是OIDC隐式流的标准声明之一,它的缺失是因为你的Amplify OAuth配置没有正确指定隐式流的响应类型。你只需要调整OAuth配置,确保responseType包含token和id_token即可:
- 更新你的
aws-exports.js或者Amplify初始化配置:
const awsconfig = { // ...其他原有配置 oauth: { domain: 'your-cognito-domain.auth.region.amazoncognito.com', scope: ['openid', 'email', 'profile'], // 必须包含openid(OIDC核心要求) redirectSignIn: '你的认证应用回调URL', redirectSignOut: '你的认证应用退出URL', responseType: 'token id_token' // 关键:指定隐式流的响应类型 } };
当你设置了这个responseType后,Cognito会识别这是隐式流请求,自动计算并在id_token中添加at_hash声明(它是access_token的哈希值片段)。
二、传入nonce并让它出现在id_token中
Amplify支持在认证请求中传入nonce,但需要手动在登录请求中传递这个参数,而不是依赖Authenticator组件的默认行为。步骤如下:
- 生成一个随机的nonce值(用于防止重放攻击),并存储在本地(比如
localStorage) - 在调用
Auth.signIn()或者Auth.federatedSignIn()时,将nonce作为参数传入 - 拿到id_token后,验证其中的nonce和你存储的是否一致
三、修改后的完整代码实现
结合上面两点,我调整了你的Login组件,同时加入了无缝SSO的逻辑(自动检测已有会话并跳转):
import React, { useEffect } from 'react'; import { Authenticator, ConfirmSignIn, SignIn } from 'aws-amplify-react'; import Amplify, { Auth } from 'aws-amplify'; import awsconfig from './aws-exports'; // 覆盖OAuth配置,确保响应类型正确 awsconfig.oauth.responseType = 'token id_token'; Amplify.configure(awsconfig); const getSearchParams = () => window.location.search.substr(1); const getValueFromSearchParam = (key) => new URLSearchParams(getSearchParams()).get(key); const getRedirectUri = () => { const redirect_uri = getValueFromSearchParam('redirect_uri'); return redirect_uri ? decodeURI(redirect_uri) : null; }; // 生成随机nonce的工具函数 const generateNonce = () => { return Math.random().toString(36).slice(2, 18); // 生成16位随机字符串 }; const Login = () => { // 组件挂载时检查已有会话,实现无缝SSO useEffect(() => { Auth.currentAuthenticatedUser({ bypassCache: false }) .then(() => handleSignedIn()) .catch(() => { // 用户未登录,生成并存储nonce const nonce = generateNonce(); localStorage.setItem('auth_nonce', nonce); }); }, []); const handleSignedIn = () => { const redirect_uri = getRedirectUri(); const state = getValueFromSearchParam('state'); if (!redirect_uri) throw new Error('No redirect_uri provided'); Auth.currentSession().then(currentSession => { const idTokenPayload = currentSession.idToken.payload; const storedNonce = localStorage.getItem('auth_nonce'); // 验证nonce一致性,防止重放攻击 if (storedNonce && idTokenPayload.nonce !== storedNonce) { throw new Error('Nonce mismatch - potential replay attack!'); } localStorage.removeItem('auth_nonce'); const id_token = currentSession.idToken.jwtToken; const access_token = currentSession.accessToken.jwtToken; const redirectUrl = `${redirect_uri}#access_token=${access_token}&id_token=${id_token}&state=${state}`; window.location.replace(redirectUrl); }).catch(err => console.error(err)); }; const handleAuthStateChange = (state) => { if (state === 'signedIn') handleSignedIn(); }; // 自定义SignIn组件,手动传递nonce const CustomSignIn = (props) => { const handleSubmit = (e) => { e.preventDefault(); const nonce = localStorage.getItem('auth_nonce'); // 调用Auth.signIn时传入nonce参数 Auth.signIn(props.username, props.password, { nonce }) .then(user => props.onStateChange('signedIn', user)) .catch(err => props.onError(err)); }; return <SignIn {...props} onSubmit={handleSubmit} />; }; return ( <Authenticator hideDefault={true} onStateChange={handleAuthStateChange}> <CustomSignIn /> <ConfirmSignIn /> </Authenticator> ); }; export default Login;
额外注意事项
- 确保你的Cognito用户池应用客户端已经开启了隐式流(在Cognito控制台的"应用客户端设置"中,勾选"隐式授权")
scope必须包含openid,否则Cognito不会返回符合OIDC标准的id_token- 无缝SSO的实现依赖于Cognito的会话Cookie,所以你的所有客户端必须在同一个顶级域名下(或者配置了Cognito的跨域会话共享)
内容的提问来源于stack exchange,提问作者OrderAndChaos
相关产品推荐
相关产品推荐

