借助AWS JS SDK实现Cognito SSO弹窗登录及令牌存储
问题描述
原始需求
我有一个配置了客户提供的Google身份提供商(IDP)的Cognito用户池,无法获取该IDP的详细信息或访问权限。目前能拿到的Cognito登录URL是:
https://<my-user-pool-url>/oauth2/authorize?identity_provide=<idp-name>&redirect_uri=<my-app-url>&response_type=TOKEN&client_id=<cognito_pool_id>
我不想让用户被重定向到这个URL再跳转回应用,希望通过弹窗让用户用Google IDP登录,从弹窗获取令牌后完成Cognito登录,或者让Cognito自动打开弹窗实现该流程,能否通过AWS JavaScript SDK实现?
我试过一种临时方案,但浏览器会阻止跨源访问弹窗地址,方案代码如下:
const popup = window.open( '<cognito_login_url_with_redirect>', 'popup', 'popup=true' ); const checkPopup = setInterval(() => { if (!popup || popup.closed) return; if (popup.window.location.href.includes('<my_redirect_url>')) { popup.close(); } clearInterval(checkPopup); const access_token = new URLSearchParams(popup.location.search).get( 'access_token' ); console.log(access_token); }, 1000);
我不想用这个方案,也不想自行管理弹窗或跨域使用postMessage,有没有办法无需重定向URI通过SSO登录Cognito?
更新1:令牌存储与认证问题
我已经成功打开弹窗并把access token传回应用,但现在卡在前端如何用这个Cognito令牌完成认证。我用的是amazon-cognito-identity-js和aws-sdk,需要通过access token登录并把所有令牌存到Cookie里,该怎么做?
我的代码如下:
const STORAGE = new CookieStorage({ domain: WEB_DOMAIN, expires: 60, secure: true }); const poolData = { UserPoolId: userPoolId, ClientId: clientId, Storage: STORAGE }; const COGNITO_USER_POOL = new CognitoUserPool(poolData); const userData = { Username: '<my_user_name>', Pool: COGNITO_USER_POOL }; const authDetails = new AuthenticationDetails({ Username: '<my_user_name>', AccessToken: accessToken });
这段代码抛出错误:NotAuthorizedException: User is not authorized to get auth details.
我还考虑过获取code而非token,再用它获取所有令牌(access、id、refresh),但不知道怎么用它完成登录并存储Cookie(不能直接存,因为需要LastAuthUser)。请问如何用access_token或其他令牌完成登录,正确存储到Cookie中并让COGNITO_USER_POOL.getCurrentUser()生效?
解决方案
一、合规实现弹窗式SSO登录
Cognito官方JS SDK没有直接封装弹窗式SSO,但可以通过调整OAuth流程配合Hosted UI实现,无需自行处理跨域问题:
- 将你的应用域名添加到Cognito用户池客户端的允许回调URL和允许退出URL列表
- 使用
response_type=CODE发起授权请求,在弹窗中打开Hosted UI授权URL,回调到应用内的空白页面(如/auth/callback) - 空白回调页面通过URL参数拿到
code,用postMessage传给父窗口后关闭弹窗 - 父窗口拿到
code后,调用Cognito的POST /oauth2/token接口换取完整令牌(access_token、id_token、refresh_token)
二、用已获取的access_token完成认证并存储令牌
你之前的错误是因为AuthenticationDetails仅用于用户名密码登录,不能直接传入access_token。正确做法是手动构造CognitoUserSession并绑定到CognitoUser实例,令牌会自动存入你配置的CookieStorage:
const STORAGE = new CookieStorage({ domain: WEB_DOMAIN, expires: 60, secure: true }); const poolData = { UserPoolId: userPoolId, ClientId: clientId, Storage: STORAGE }; const COGNITO_USER_POOL = new CognitoUserPool(poolData); // 假设已获取到access_token、id_token、refresh_token const accessToken = new CognitoAccessToken({ AccessToken: '<你的access_token>' }); const idToken = new CognitoIdToken({ IdToken: '<你的id_token>' }); const refreshToken = new CognitoRefreshToken({ RefreshToken: '<你的refresh_token>' }); // 构造用户会话 const userSession = new CognitoUserSession({ AccessToken: accessToken, IdToken: idToken, RefreshToken: refreshToken }); // 从id_token解析用户名(也可从弹窗回调中直接获取) const username = idToken.payload.username; // 创建CognitoUser实例 const cognitoUser = new CognitoUser({ Username: username, Pool: COGNITO_USER_POOL }); // 设置会话,令牌自动存入CookieStorage cognitoUser.setSignInUserSession(userSession); // 验证生效状态 const currentUser = COGNITO_USER_POOL.getCurrentUser(); if (currentUser) { currentUser.getSession((err, session) => { if (!err) { console.log('认证成功,会话有效:', session); } }); }
三、授权code换令牌的实现
如果用response_type=CODE拿到授权码,需先换取完整令牌:
// 调用Cognito令牌接口 fetch(`https://<my-user-pool-url>/oauth2/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', client_id: '<cognito_client_id>', code: '<拿到的授权code>', redirect_uri: '<你的回调URL>' }) }) .then(res => res.json()) .then(data => { // 用返回的令牌构造会话,后续步骤同上述方案 const accessToken = new CognitoAccessToken({ AccessToken: data.access_token }); const idToken = new CognitoIdToken({ IdToken: data.id_token }); const refreshToken = new CognitoRefreshToken({ RefreshToken: data.refresh_token }); // ...构造会话并绑定到CognitoUser实例 });
关键注意事项
- 确保回调URL在Cognito用户池客户端的允许列表中,否则会被拦截
- CookieStorage的
domain和secure属性必须与应用域名匹配,否则无法正常存储 - 弹窗回调页面需与父窗口同域,避免
postMessage被浏览器阻止
内容的提问来源于stack exchange,提问作者hido

