当AWS Cognito要求用户进行多因素身份验证时的身份验证流程

7
我正在尝试为AWS Cognito中设备管理的现有解决方案(使用Angular构建)添加MFA以进行用户身份验证。
但是,我在处理这个特定响应时遇到了困难,从用户体验角度来看,它实际上感觉很不完整,因此我希望如果其他人在这里有经验痛点的话,能提供帮助。
请参见用例23以获取示例实现,我的代码如下:
authenticate(username: string, password: string): Observable<any> {

    // init cognitoUser here

    return new Observable((observer) => {
        cognitoUser.authenticateUser(authenticationDetails, {
            onSuccess: (result: any) => {},
            onFailure: (err: Error) => {},
            mfaRequired: (codeDeliveryDetails: any) => {

                // SMS has just been sent automatically 
                // and it needs to be confirmed within this scope

                // The example linked requests the code via `confirm()`
                // which is awful UX...and since this is a service
                // probably non-compliant with best practice
                // However, without this `confirm` at this point in                     
                // time, we have no confirmationCode below

                cognitoUser.sendMFACode(confirmationCode, {
                    onSuccess: (result) => {
                        observer.next(result);
                        observer.complete();
                    }, onFailure: (err: Error) => {
                        observer.error(err);
                        observer.complete();
                    }
                });
            }
        });
    });
}

预期结果:

  • 如果用户成功验证但尚未通过 MFA 添加此设备,则我们可以管理重定向到适当的确认代码表单页并手动触发sendMFACode函数(可能通过某种有限会话?)

问题:

  • 我们没有会话,因此无法在此登录屏幕之外自动发送的MFA代码中询问用户...陷入困境?
  • 在登录表单中添加另一个显示/隐藏字段不起作用,因为它会多次触发sendMfaCode函数,导致发送多个SMS代码。

是否有人已经成功跳出此流程?


这显然是预期的用法,但他们理解了用户体验问题,并已标记为新功能请求 - https://github.com/aws/aws-amplify/issues/382 - Matt Rowles
1
目前来看,AWS似乎并没有认真对待这个问题。我会建议那些想要使用Cognito和MFA的人们做好准备,将整个堆栈转换为React,以便在用户体验方面至少有一些控制权,或者使用其他身份验证服务。 - Matt Rowles
2个回答

9

虽然我相信amazon-cognito-identity-js API是由非常有才华的人员开发的,但它的设计实在是太糟糕了。这就是为什么它已经被弃用的原因。我的个人建议是迁移到Amplify,这可以让我感到更加不生气。

使用Amplify,您可以执行以下操作。

import Amplify from 'aws-amplify'
import Auth from '@aws-amplify/auth'

let mfaRequired = false

Amplify.configure({
    Auth: {
        userPoolWebClientId: '',
        userPoolId: ''
    }
})

const logUserIn = (user) => {
  // Go forth and be happy
}

// Run me on your login form's submit event
const login = async (username, password) => {
  const user = await Auth.signIn(username, password)

  if (user.challengeName === 'SMS_MFA') {
    // Change UI to show MFA Code input
    mfaRequired = true
    return
  }
  return logUserIn(user)
}

// Run me when the user submits theire MFA code
const senfMfaCode = async (mfaCode) => {
  const user = await Auth.confirmSignIn(mfaCode)
  return logUserIn(user)
}

但是如果出于某种悲哀的原因,您需要继续使用amazon-cognito-identity-js,不用担心。我可以帮助您。

只需在回调之外存储cognitoUser对象。文档有点误导,因为它只显示自包含示例,但没有理由不通知您的UI需要MFA,然后稍后调用cognitoUser.sendMFACode()

请记住,文档显示将this传递给sendMFACode()进行作用域(这很糟糕),但是您可以将回调声明为变量,并在authenticateUser()sendMFACode()函数之间(或者其他任意数量的函数之间)共享。

import { CognitoUserPool, AuthenticationDetails, CognitoUser } from 'amazon-cognito-identity-js'

export let mfaRequired = false
export let cognitoUser = null

export const cognitoCallbacks = {
  mfaRequired () {
    // Implement you functionality to show UI for MFA form
    mfaRequired = true
  },
  onSuccess (response) {
    // Dance for joy the code gods be glorious.
  },
  onFailure () {
    // Cry.
  }
}

export const logUserIn = payload => {
  cognitoUser = new CognitoUser({
    Username: 'Matt Damon',
    Pool: new CognitoUserPool({
      UserPoolId: '',
      ClientId: ''
    })
  })
  return cognitoUser.authenticateUser(new AuthenticationDetails(payload), cognitoCallbacks)
}

export const sendMfaCode = MFACode => {
  cognitoUser.sendMFACode(MFACode, cognitoCallbacks)
}

这只是一个非常基础的实现,除此之外,您还可以:

  1. 在外部模块中覆盖 mfaRequired 函数,以执行您想要的任何操作。
  2. 将整个过程封装在发布/订阅插件中,并订阅事件。

希望这有所帮助!


太好了,非常感谢@stwilz!真是个传奇。我会在周末试一下。感谢详细的回复。 - Matt Rowles
3
没问题。我是即兴编写了那段代码,如果你需要一个可运行的示例,我可以通过JSFiddle链接给你。 - stwilz
我正在尝试类似的东西,但是使用TOTP(而不是短信)和托管的用户界面。当我使用托管的用户界面注册时,它应该显示或回复带有用于TOTP设置的密钥,但是我在屏幕上或ajax响应中都没有看到任何密钥。我在这里漏掉了什么? - Anjan Biswas
我已将MFA设置为可选项。因此,对于某些用户,在注册时未收到MFA。如何使用aws-amplify-react-native触发它? - sejn
小心!如果cognitoUser对象存储在后端变量中,它将被另一个几乎同时登录的用户覆盖。因此,您可能会在错误的用户上调用sendMFACode - Jan
@Jan,您能否提供一个梗概,演示错误的用户是如何被调用的? - stwilz

2
我知道这是一个老问题,但我认为我的答案对于仍在使用amazon-cognito-identity-js API而不是Amplify的人可能有所帮助。@stwilz的答案有些作用,但是当您偏离文档的用例时(并且在进行TOTP MFA而不是SMS MFA时可能会出现),会出现一些复杂情况。我创建了一种解决方法来解决可能出现错误的情况,例如Invalid Access TokenMissing parameter SessionInvalid session for the user
如果您需要在回调之外使用像sendMFACode这样的东西,则仅将cognitoUser存储在回调之外并不足够。实际上,您必须再次调用authenticateUser函数,然后在回调中调用sendMFACode。对于TOTP的verifySoftwareToken更加复杂,在这种情况下,您实际上必须存储Cognito用户对象,然后在再次调用authenticateUser时重新分配它。
如果所有这些都没有意义,我已经创建了一个简单的Github Gist,使用React和amazon-cognito-identity-js来展示这样的流程将如何工作。在这里:https://gist.github.com/harve27/807597824720d0919476c0262e30f587

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接