Gems/AWSClientAuth/Code/Source/Authentication/AWSCognitoAuthenticationProvider.cpp (203 lines of code) (raw):
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/smart_ptr/weak_ptr.h>
#include <AzCore/Jobs/JobFunction.h>
#include <Authentication/AWSCognitoAuthenticationProvider.h>
#include <Authentication/AuthenticationProviderTypes.h>
#include <Authentication/AuthenticationProviderBus.h>
#include <AWSClientAuthBus.h>
#include <AWSCoreBus.h>
#include <ResourceMapping/AWSResourceMappingBus.h>
#include <AWSClientAuthResourceMappingConstants.h>
#include <aws/cognito-idp/model/InitiateAuthRequest.h>
#include <aws/cognito-idp/model/InitiateAuthResult.h>
#include <aws/cognito-idp/model/RespondToAuthChallengeRequest.h>
#include <aws/cognito-idp/model/RespondToAuthChallengeResult.h>
#include <aws/cognito-idp/CognitoIdentityProviderClient.h>
#include <aws/cognito-idp/CognitoIdentityProviderErrors.h>
namespace AWSClientAuth
{
constexpr char CognitoUsernameKey[] = "USERNAME";
constexpr char CognitoPasswordKey[] = "PASSWORD";
constexpr char CognitoRefreshTokenAuthParamKey[] = "REFRESH_TOKEN";
constexpr char CognitoSmsMfaCodeKey[] = "SMS_MFA_CODE";
bool AWSCognitoAuthenticationProvider::Initialize()
{
AWSCore::AWSResourceMappingRequestBus::BroadcastResult(
m_cognitoAppClientId, &AWSCore::AWSResourceMappingRequests::GetResourceNameId, CognitoAppClientIdResourceMappingKey);
AZ_Warning("AWSCognitoAuthenticationProvider", !m_cognitoAppClientId.empty(), "Missing Cognito App Client Id from resource mappings. Calls to Cognito will fail.");
return !m_cognitoAppClientId.empty();
}
void AWSCognitoAuthenticationProvider::PasswordGrantSingleFactorSignInAsync(const AZStd::string& username, const AZStd::string& password)
{
InitiateAuthInternalAsync(username, password, [this](Aws::CognitoIdentityProvider::Model::InitiateAuthOutcome initiateAuthOutcome)
{
if (initiateAuthOutcome.IsSuccess())
{
Aws::CognitoIdentityProvider::Model::InitiateAuthResult initiateAuthResult{ initiateAuthOutcome.GetResult() };
if (initiateAuthResult.GetChallengeName() == Aws::CognitoIdentityProvider::Model::ChallengeNameType::NOT_SET)
{
Aws::CognitoIdentityProvider::Model::AuthenticationResultType authenticationResult = initiateAuthResult.GetAuthenticationResult();
UpdateTokens(authenticationResult);
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnPasswordGrantSingleFactorSignInSuccess,
AuthenticationTokens(authenticationResult.GetAccessToken().c_str(),
authenticationResult.GetRefreshToken().c_str(), authenticationResult.GetIdToken().c_str(),
ProviderNameEnum::AWSCognitoIDP, authenticationResult.GetExpiresIn()));
}
else
{
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnPasswordGrantSingleFactorSignInFail
, AZStd::string::format("Unexpected Challenge type: %s"
, Aws::CognitoIdentityProvider::Model::ChallengeNameTypeMapper::GetNameForChallengeNameType(initiateAuthResult.GetChallengeName()).c_str()));
}
}
else
{
Aws::Client::AWSError<Aws::CognitoIdentityProvider::CognitoIdentityProviderErrors> error = initiateAuthOutcome.GetError();
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnPasswordGrantSingleFactorSignInFail, error.GetMessage().c_str());
}
});
}
void AWSCognitoAuthenticationProvider::PasswordGrantMultiFactorSignInAsync(const AZStd::string& username, const AZStd::string& password)
{
InitiateAuthInternalAsync(username, password, [this](Aws::CognitoIdentityProvider::Model::InitiateAuthOutcome initiateAuthOutcome)
{
if (initiateAuthOutcome.IsSuccess())
{
Aws::CognitoIdentityProvider::Model::InitiateAuthResult initiateAuthResult{ initiateAuthOutcome.GetResult() };
if (initiateAuthResult.GetChallengeName() == Aws::CognitoIdentityProvider::Model::ChallengeNameType::SMS_MFA)
{
Aws::CognitoIdentityProvider::Model::AuthenticationResultType authenticationResult = initiateAuthResult.GetAuthenticationResult();
// Call on sign in success for MFA
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnPasswordGrantMultiFactorSignInSuccess);
m_session = initiateAuthResult.GetSession().c_str();
}
else
{
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnPasswordGrantMultiFactorSignInFail
, AZStd::string::format("Unexpected Challenge type: %s"
, Aws::CognitoIdentityProvider::Model::ChallengeNameTypeMapper::GetNameForChallengeNameType(initiateAuthResult.GetChallengeName()).c_str()));
}
}
else
{
Aws::Client::AWSError<Aws::CognitoIdentityProvider::CognitoIdentityProviderErrors> error = initiateAuthOutcome.GetError();
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnPasswordGrantMultiFactorSignInFail, error.GetMessage().c_str());
}
});
}
// Call RespondToAuthChallenge for Cognito authentication flow.
// Refer https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html.
void AWSCognitoAuthenticationProvider::PasswordGrantMultiFactorConfirmSignInAsync(const AZStd::string& username, const AZStd::string& confirmationCode)
{
std::shared_ptr<Aws::CognitoIdentityProvider::CognitoIdentityProviderClient> cognitoIdentityProviderClient =
AZ::Interface<IAWSClientAuthRequests>::Get()->GetCognitoIDPClient();
AZ::JobContext* jobContext = nullptr;
AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext);
AZ::Job* confirmSignInJob = AZ::CreateJobFunction([this, cognitoIdentityProviderClient, confirmationCode, username]()
{
// Set Request parameters for SMS Multi factor authentication.
// Note: Email MFA is no longer supported by Cognito, use SMS as MFA
Aws::CognitoIdentityProvider::Model::RespondToAuthChallengeRequest respondToAuthChallengeRequest;
respondToAuthChallengeRequest.SetClientId(m_cognitoAppClientId.c_str());
respondToAuthChallengeRequest.AddChallengeResponses(CognitoSmsMfaCodeKey, confirmationCode.c_str());
respondToAuthChallengeRequest.AddChallengeResponses(CognitoUsernameKey, username.c_str());
respondToAuthChallengeRequest.SetChallengeName(Aws::CognitoIdentityProvider::Model::ChallengeNameType::SMS_MFA);
respondToAuthChallengeRequest.SetSession(m_session.c_str());
Aws::CognitoIdentityProvider::Model::RespondToAuthChallengeOutcome respondToAuthChallengeOutcome{ cognitoIdentityProviderClient->RespondToAuthChallenge(respondToAuthChallengeRequest) };
if (respondToAuthChallengeOutcome.IsSuccess())
{
Aws::CognitoIdentityProvider::Model::RespondToAuthChallengeResult respondToAuthChallengeResult{ respondToAuthChallengeOutcome.GetResult() };
if (respondToAuthChallengeResult.GetChallengeName() == Aws::CognitoIdentityProvider::Model::ChallengeNameType::NOT_SET)
{
Aws::CognitoIdentityProvider::Model::AuthenticationResultType authenticationResult = respondToAuthChallengeResult.GetAuthenticationResult();
UpdateTokens(authenticationResult);
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnPasswordGrantMultiFactorConfirmSignInSuccess,
AuthenticationTokens(authenticationResult.GetAccessToken().c_str(),
authenticationResult.GetRefreshToken().c_str(), authenticationResult.GetIdToken().c_str(),
ProviderNameEnum::AWSCognitoIDP, authenticationResult.GetExpiresIn()));
}
}
else
{
Aws::Client::AWSError<Aws::CognitoIdentityProvider::CognitoIdentityProviderErrors> error = respondToAuthChallengeOutcome.GetError();
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnPasswordGrantMultiFactorConfirmSignInFail, error.GetMessage().c_str());
}
}, true, jobContext);
confirmSignInJob->Start();
}
void AWSCognitoAuthenticationProvider::DeviceCodeGrantSignInAsync()
{
AZ_Assert(false, "Not supported");
}
void AWSCognitoAuthenticationProvider::DeviceCodeGrantConfirmSignInAsync()
{
AZ_Assert(false, "Not supported");
}
void AWSCognitoAuthenticationProvider::RefreshTokensAsync()
{
std::shared_ptr<Aws::CognitoIdentityProvider::CognitoIdentityProviderClient> cognitoIdentityProviderClient =
AZ::Interface<IAWSClientAuthRequests>::Get()->GetCognitoIDPClient();
AZ::JobContext* jobContext = nullptr;
AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext);
AZ::Job* initiateAuthJob = AZ::CreateJobFunction([this, cognitoIdentityProviderClient]()
{
// Set Request parameters.
Aws::CognitoIdentityProvider::Model::InitiateAuthRequest initiateAuthRequest;
initiateAuthRequest.SetClientId(m_cognitoAppClientId.c_str());
initiateAuthRequest.SetAuthFlow(Aws::CognitoIdentityProvider::Model::AuthFlowType::REFRESH_TOKEN_AUTH);
// Set username and password for Password grant/ Initiate Auth flow.
Aws::Map<Aws::String, Aws::String> authParameters
{
{CognitoRefreshTokenAuthParamKey, GetAuthenticationTokens().GetRefreshToken().c_str()}
};
initiateAuthRequest.SetAuthParameters(authParameters);
Aws::CognitoIdentityProvider::Model::InitiateAuthOutcome initiateAuthOutcome{ cognitoIdentityProviderClient->InitiateAuth(initiateAuthRequest) };
if (initiateAuthOutcome.IsSuccess())
{
Aws::CognitoIdentityProvider::Model::InitiateAuthResult initiateAuthResult{ initiateAuthOutcome.GetResult() };
if (initiateAuthResult.GetChallengeName() == Aws::CognitoIdentityProvider::Model::ChallengeNameType::NOT_SET)
{
Aws::CognitoIdentityProvider::Model::AuthenticationResultType authenticationResult = initiateAuthResult.GetAuthenticationResult();
UpdateTokens(authenticationResult);
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnRefreshTokensSuccess,
AuthenticationTokens(authenticationResult.GetAccessToken().c_str(),
authenticationResult.GetRefreshToken().c_str(), authenticationResult.GetIdToken().c_str(),
ProviderNameEnum::AWSCognitoIDP, authenticationResult.GetExpiresIn()));
}
else
{
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnRefreshTokensFail
, AZStd::string::format("Unexpected Challenge type: %s"
, Aws::CognitoIdentityProvider::Model::ChallengeNameTypeMapper::GetNameForChallengeNameType(initiateAuthResult.GetChallengeName()).c_str()));
}
}
else
{
Aws::Client::AWSError<Aws::CognitoIdentityProvider::CognitoIdentityProviderErrors> error = initiateAuthOutcome.GetError();
AuthenticationProviderNotificationBus::Broadcast(&AuthenticationProviderNotifications::OnRefreshTokensFail, error.GetMessage().c_str());
}
}, true, jobContext);
initiateAuthJob->Start();
}
// Call InitiateAuth for Cognito authentication flow.
// Refer https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html.
void AWSCognitoAuthenticationProvider::InitiateAuthInternalAsync(const AZStd::string& username, const AZStd::string& password
, AZStd::function<void(Aws::CognitoIdentityProvider::Model::InitiateAuthOutcome outcome)> outcomeCallback)
{
std::shared_ptr<Aws::CognitoIdentityProvider::CognitoIdentityProviderClient> cognitoIdentityProviderClient =
AZ::Interface<IAWSClientAuthRequests>::Get()->GetCognitoIDPClient();
AZ::JobContext* jobContext = nullptr;
AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext);
AZ::Job* initiateAuthJob = AZ::CreateJobFunction([this, cognitoIdentityProviderClient, username, password, outcomeCallback]()
{
// Set Request parameters.
Aws::CognitoIdentityProvider::Model::InitiateAuthRequest initiateAuthRequest;
initiateAuthRequest.SetClientId(m_cognitoAppClientId.c_str());
initiateAuthRequest.SetAuthFlow(Aws::CognitoIdentityProvider::Model::AuthFlowType::USER_PASSWORD_AUTH);
// Set username and password for Password grant/ Initiate Auth flow.
Aws::Map<Aws::String, Aws::String> authParameters
{
{CognitoUsernameKey, username.c_str()},
{CognitoPasswordKey, password.c_str()}
};
initiateAuthRequest.SetAuthParameters(authParameters);
Aws::CognitoIdentityProvider::Model::InitiateAuthOutcome initiateAuthOutcome{ cognitoIdentityProviderClient->InitiateAuth(initiateAuthRequest) };
outcomeCallback(initiateAuthOutcome);
}, true, jobContext);
initiateAuthJob->Start();
}
void AWSCognitoAuthenticationProvider::UpdateTokens(const Aws::CognitoIdentityProvider::Model::AuthenticationResultType& authenticationResult)
{
// Storing authentication tokens in memory can be a security concern. The access token and id token are not actually in use by
// the authentication provider and shouldn't be stored in the member variable.
m_authenticationTokens = AuthenticationTokens("", authenticationResult.GetRefreshToken().c_str(),
"", ProviderNameEnum::AWSCognitoIDP,
authenticationResult.GetExpiresIn());
}
} // namespace AWSClientAuth