/*
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
/* The following knowledge base was used as guide for the implementation
* of some of the below Cognito challenges.
* https://aws.amazon.com/premiumsupport/knowledge-center/cognito-user-pool-remembered-devices/?nc1=h_ls
*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Amazon.CognitoIdentity;
using Amazon.CognitoIdentityProvider;
using Amazon.CognitoIdentityProvider.Model;
using Amazon.Extensions.CognitoAuthentication.Util;
namespace Amazon.Extensions.CognitoAuthentication
{
partial class CognitoUser
{
///
/// Initiates the asynchronous SRP authentication flow
///
/// InitiateSrpAuthRequest object containing the necessary parameters to
/// create an InitiateAuthAsync API call for SRP authentication
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task StartWithSrpAuthAsync(InitiateSrpAuthRequest srpRequest)
{
return await StartWithSrpAuthAsync(srpRequest, default);
}
///
/// Initiates the asynchronous SRP authentication flow
///
/// InitiateSrpAuthRequest object containing the necessary parameters to
/// create an InitiateAuthAsync API call for SRP authentication
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task StartWithSrpAuthAsync(InitiateSrpAuthRequest srpRequest, CancellationToken cancellationToken)
{
if (srpRequest == null || string.IsNullOrEmpty(srpRequest.Password))
{
throw new ArgumentNullException("Password required for authentication.", "srpRequest");
}
Tuple tupleAa = AuthenticationHelper.CreateAaTuple();
InitiateAuthRequest initiateRequest = CreateSrpAuthRequest(tupleAa);
if (srpRequest.IsCustomAuthFlow)
{
initiateRequest.AuthFlow = AuthFlowType.CUSTOM_AUTH;
initiateRequest.AuthParameters.Add("CHALLENGE_NAME", "SRP_A");
}
InitiateAuthResponse initiateResponse = await Provider.InitiateAuthAsync(initiateRequest, cancellationToken).ConfigureAwait(false);
UpdateUsernameAndSecretHash(initiateResponse.ChallengeParameters);
RespondToAuthChallengeRequest challengeRequest =
CreateSrpPasswordVerifierAuthRequest(initiateResponse, srpRequest.Password, tupleAa);
if (srpRequest.ClientMetadata != null)
{
challengeRequest.ClientMetadata = new Dictionary(srpRequest.ClientMetadata);
}
bool challengeResponsesValid = challengeRequest != null && challengeRequest.ChallengeResponses != null;
bool deviceKeyValid = Device != null && !string.IsNullOrEmpty(Device.DeviceKey);
if (challengeResponsesValid && deviceKeyValid)
{
challengeRequest.ChallengeResponses[CognitoConstants.ChlgParamDeviceKey] = Device.DeviceKey;
}
RespondToAuthChallengeResponse verifierResponse =
await Provider.RespondToAuthChallengeAsync(challengeRequest, cancellationToken).ConfigureAwait(false);
var isDeviceAuthRequest = verifierResponse.AuthenticationResult == null && (!string.IsNullOrEmpty(srpRequest.DeviceGroupKey)
|| !string.IsNullOrEmpty(srpRequest.DevicePass));
#region Device-level authentication
if (isDeviceAuthRequest)
{
if (string.IsNullOrEmpty(srpRequest.DeviceGroupKey) || string.IsNullOrEmpty(srpRequest.DevicePass))
{
throw new ArgumentNullException("Device Group Key and Device Pass required for authentication.", "srpRequest");
}
#region Device SRP Auth
var deviceAuthRequest = CreateDeviceSrpAuthRequest(verifierResponse, tupleAa);
var deviceAuthResponse = await Provider.RespondToAuthChallengeAsync(deviceAuthRequest, cancellationToken).ConfigureAwait(false);
#endregion
#region Device Password Verifier
var devicePasswordChallengeRequest = CreateDevicePasswordVerifierAuthRequest(deviceAuthResponse, srpRequest.DeviceGroupKey, srpRequest.DevicePass, tupleAa);
verifierResponse = await Provider.RespondToAuthChallengeAsync(devicePasswordChallengeRequest, cancellationToken).ConfigureAwait(false);
#endregion
}
#endregion
UpdateSessionIfAuthenticationComplete(verifierResponse.ChallengeName, verifierResponse.AuthenticationResult);
return new AuthFlowResponse(verifierResponse.Session,
verifierResponse.AuthenticationResult,
verifierResponse.ChallengeName,
verifierResponse.ChallengeParameters,
new Dictionary(verifierResponse.ResponseMetadata.Metadata));
}
///
/// Internal method which responds to the DEVICE_SRP_AUTH challenge in SRP authentication
///
/// The response from the PASSWORD_VERIFIER challenge
/// Tuple of BigIntegers containing the A,a pair for the SRP protocol flow
///
private RespondToAuthChallengeRequest CreateDeviceSrpAuthRequest(RespondToAuthChallengeResponse challenge, Tuple tupleAa)
{
RespondToAuthChallengeRequest authChallengeRequest = new RespondToAuthChallengeRequest()
{
ChallengeName = "DEVICE_SRP_AUTH",
ClientId = ClientID,
Session = challenge.Session,
ChallengeResponses = new Dictionary
{
{CognitoConstants.ChlgParamUsername, Username},
{CognitoConstants.ChlgParamDeviceKey, Device.DeviceKey},
{CognitoConstants.ChlgParamSrpA, tupleAa.Item1.ToString("X") },
}
};
if (!string.IsNullOrEmpty(ClientSecret))
{
SecretHash = CognitoAuthHelper.GetUserPoolSecretHash(Username, ClientID, ClientSecret);
authChallengeRequest.ChallengeResponses.Add(CognitoConstants.ChlgParamSecretHash, SecretHash);
}
return authChallengeRequest;
}
///
/// Internal method which responds to the DEVICE_PASSWORD_VERIFIER challenge in SRP authentication
///
/// Response from the InitiateAuth challenge
/// Group Key for the CognitoDevice, needed for authentication
/// Password for the CognitoDevice, needed for authentication
/// Tuple of BigIntegers containing the A,a pair for the SRP protocol flow
/// Returns the RespondToAuthChallengeRequest for an SRP authentication flow
private RespondToAuthChallengeRequest CreateDevicePasswordVerifierAuthRequest(RespondToAuthChallengeResponse challenge,
string deviceKeyGroup,
string devicePassword,
Tuple tupleAa)
{
string deviceKey = challenge.ChallengeParameters[CognitoConstants.ChlgParamDeviceKey];
string username = challenge.ChallengeParameters[CognitoConstants.ChlgParamUsername];
string secretBlock = challenge.ChallengeParameters[CognitoConstants.ChlgParamSecretBlock];
string salt = challenge.ChallengeParameters[CognitoConstants.ChlgParamSalt];
BigInteger srpb = BigIntegerExtensions.FromUnsignedLittleEndianHex(challenge.ChallengeParameters[CognitoConstants.ChlgParamSrpB]);
if ((srpb.TrueMod(AuthenticationHelper.N)).Equals(BigInteger.Zero))
{
throw new ArgumentException("SRP error, B mod N cannot be zero.", "challenge");
}
string timeStr = DateTime.UtcNow.ToString("ddd MMM d HH:mm:ss \"UTC\" yyyy", CultureInfo.InvariantCulture);
var claimBytes = AuthenticationHelper.AuthenticateDevice(username, deviceKey, devicePassword, deviceKeyGroup, salt,
challenge.ChallengeParameters[CognitoConstants.ChlgParamSrpB], secretBlock, timeStr, tupleAa);
string claimB64 = Convert.ToBase64String(claimBytes);
Dictionary srpAuthResponses = new Dictionary(StringComparer.Ordinal)
{
{CognitoConstants.ChlgParamPassSecretBlock, secretBlock},
{CognitoConstants.ChlgParamPassSignature, claimB64},
{CognitoConstants.ChlgParamUsername, username },
{CognitoConstants.ChlgParamTimestamp, timeStr },
{CognitoConstants.ChlgParamDeviceKey, Device.DeviceKey }
};
if (!string.IsNullOrEmpty(ClientSecret))
{
SecretHash = CognitoAuthHelper.GetUserPoolSecretHash(Username, ClientID, ClientSecret);
srpAuthResponses.Add(CognitoConstants.ChlgParamSecretHash, SecretHash);
}
RespondToAuthChallengeRequest authChallengeRequest = new RespondToAuthChallengeRequest()
{
ChallengeName = challenge.ChallengeName,
ClientId = ClientID,
Session = challenge.Session,
ChallengeResponses = srpAuthResponses
};
return authChallengeRequest;
}
///
/// Initiates the asynchronous custom authentication flow
///
/// InitiateCustomAuthRequest object containing the necessary parameters to
/// create an InitiateAuthAsync API call for custom authentication
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task StartWithCustomAuthAsync(InitiateCustomAuthRequest customRequest)
{
return await StartWithCustomAuthAsync(customRequest, default);
}
///
/// Initiates the asynchronous custom authentication flow
///
/// InitiateCustomAuthRequest object containing the necessary parameters to
/// create an InitiateAuthAsync API call for custom authentication
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task StartWithCustomAuthAsync(InitiateCustomAuthRequest customRequest, CancellationToken cancellationToken)
{
InitiateAuthRequest authRequest = new InitiateAuthRequest()
{
AuthFlow = AuthFlowType.CUSTOM_AUTH,
AuthParameters = new Dictionary(customRequest.AuthParameters),
ClientId = ClientID,
ClientMetadata = new Dictionary(customRequest.ClientMetadata)
};
InitiateAuthResponse initiateResponse = await Provider.InitiateAuthAsync(authRequest, cancellationToken).ConfigureAwait(false);
UpdateUsernameAndSecretHash(initiateResponse.ChallengeParameters);
UpdateSessionIfAuthenticationComplete(initiateResponse.ChallengeName, initiateResponse.AuthenticationResult);
return new AuthFlowResponse(initiateResponse.Session,
initiateResponse.AuthenticationResult,
initiateResponse.ChallengeName,
initiateResponse.ChallengeParameters,
new Dictionary(initiateResponse.ResponseMetadata.Metadata));
}
///
/// Uses the properties of the RespondToCustomChallengeRequest object to respond to the current
/// custom authentication challenge using an asynchronous call
///
/// RespondToCustomChallengeRequest object containing the necessary parameters to
/// respond to the current custom authentication challenge
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task RespondToCustomAuthAsync(RespondToCustomChallengeRequest customRequest)
{
return await RespondToCustomAuthAsync(customRequest, default);
}
///
/// Uses the properties of the RespondToCustomChallengeRequest object to respond to the current
/// custom authentication challenge using an asynchronous call
///
/// RespondToCustomChallengeRequest object containing the necessary parameters to
/// respond to the current custom authentication challenge
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task RespondToCustomAuthAsync(RespondToCustomChallengeRequest customRequest, CancellationToken cancellationToken)
{
RespondToAuthChallengeRequest request = new RespondToAuthChallengeRequest()
{
ChallengeName = ChallengeNameType.CUSTOM_CHALLENGE,
ClientId = ClientID,
ChallengeResponses = new Dictionary(customRequest.ChallengeParameters),
ClientMetadata = new Dictionary(customRequest.ClientMetadata),
Session = customRequest.SessionID
};
RespondToAuthChallengeResponse authResponse =
await Provider.RespondToAuthChallengeAsync(request, cancellationToken).ConfigureAwait(false);
UpdateSessionIfAuthenticationComplete(authResponse.ChallengeName, authResponse.AuthenticationResult);
return new AuthFlowResponse(authResponse.Session,
authResponse.AuthenticationResult,
authResponse.ChallengeName,
authResponse.ChallengeParameters,
new Dictionary(authResponse.ResponseMetadata.Metadata));
}
///
/// Generates a DeviceSecretVerifierConfigType object for a device associated with a CognitoUser for SRP Authentication
///
/// The DeviceKey Group for the associated CognitoDevice
/// The DeviceKey for the associated CognitoDevice
/// The random password for the associated CognitoDevice
///
public DeviceSecretVerifierConfigType GenerateDeviceVerifier(string deviceGroupKey, string devicePass, string username)
{
return AuthenticationHelper.GenerateDeviceVerifier(deviceGroupKey, devicePass, username);
}
///
/// Sends a confirmation request to Cognito for a new CognitoDevice
///
/// The user pool access token for from the InitiateAuth or other challenge response
/// The device key for the associated CognitoDevice
/// The friendly name to be associated with the corresponding CognitoDevice
/// The password verifier generated from GenerateDeviceVerifier for the corresponding CognitoDevice
/// The salt generated from GenerateDeviceVerifier for the corresponding CognitoDevice
///
public async Task ConfirmDeviceAsync(string accessToken, string deviceKey, string deviceName, string passwordVerifier, string salt)
{
return await ConfirmDeviceAsync(accessToken, deviceKey, deviceName, passwordVerifier, salt, default);
}
///
/// Sends a confirmation request to Cognito for a new CognitoDevice
///
/// The user pool access token for from the InitiateAuth or other challenge response
/// The device key for the associated CognitoDevice
/// The friendly name to be associated with the corresponding CognitoDevice
/// The password verifier generated from GenerateDeviceVerifier for the corresponding CognitoDevice
/// The salt generated from GenerateDeviceVerifier for the corresponding CognitoDevice
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
///
public async Task ConfirmDeviceAsync(string accessToken, string deviceKey, string deviceName, string passwordVerifier, string salt, CancellationToken cancellationToken)
{
var request = new ConfirmDeviceRequest
{
AccessToken = accessToken,
DeviceKey = deviceKey,
DeviceName = deviceName,
DeviceSecretVerifierConfig = new DeviceSecretVerifierConfigType
{
PasswordVerifier = passwordVerifier,
Salt = salt
}
};
return await Provider.ConfirmDeviceAsync(request, cancellationToken);
}
///
/// Updates the remembered status for a given CognitoDevice
///
/// The user pool access token for from the InitiateAuth or other challenge response
/// The device key for the associated CognitoDevice
/// The device remembered status for the associated CognitoDevice
///
public async Task UpdateDeviceStatusAsync(string accessToken, string deviceKey, string deviceRememberedStatus)
{
return await UpdateDeviceStatusAsync(accessToken, deviceKey, deviceRememberedStatus, default);
}
///
/// Updates the remembered status for a given CognitoDevice
///
/// The user pool access token for from the InitiateAuth or other challenge response
/// The device key for the associated CognitoDevice
/// The device remembered status for the associated CognitoDevice
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
///
public async Task UpdateDeviceStatusAsync(string accessToken, string deviceKey, string deviceRememberedStatus, CancellationToken cancellationToken)
{
var request = new UpdateDeviceStatusRequest
{
AccessToken = accessToken,
DeviceKey = deviceKey,
DeviceRememberedStatus = deviceRememberedStatus
};
return await Provider.UpdateDeviceStatusAsync(request, cancellationToken);
}
///
/// Uses the properties of the RespondToSmsMfaRequest object to respond to the current MFA
/// authentication challenge using an asynchronous call
///
/// RespondToSmsMfaRequest object containing the necessary parameters to
/// respond to the current SMS MFA authentication challenge
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task RespondToSmsMfaAuthAsync(RespondToSmsMfaRequest smsMfaRequest)
{
return await RespondToSmsMfaAuthAsync(smsMfaRequest, default);
}
///
/// Uses the properties of the RespondToSmsMfaRequest object to respond to the current MFA
/// authentication challenge using an asynchronous call
///
/// RespondToSmsMfaRequest object containing the necessary parameters to
/// respond to the current SMS MFA authentication challenge
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task RespondToSmsMfaAuthAsync(RespondToSmsMfaRequest smsMfaRequest, CancellationToken cancellationToken)
{
return await RespondToMfaAuthAsync(smsMfaRequest, cancellationToken).ConfigureAwait(false);
}
///
/// Uses the properties of the RespondToSmsMfaRequest object to respond to the current MFA
/// authentication challenge using an asynchronous call
///
/// RespondToMfaRequest object containing the necessary parameters to
/// respond to the current MFA authentication challenge
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public async Task RespondToMfaAuthAsync(RespondToMfaRequest mfaRequest)
{
return await RespondToMfaAuthAsync(mfaRequest, default);
}
///
/// Uses the properties of the RespondToSmsMfaRequest object to respond to the current MFA
/// authentication challenge using an asynchronous call
///
/// RespondToMfaRequest object containing the necessary parameters to
/// respond to the current MFA authentication challenge
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public async Task RespondToMfaAuthAsync(RespondToMfaRequest mfaRequest, CancellationToken cancellationToken)
{
RespondToAuthChallengeRequest challengeRequest = new RespondToAuthChallengeRequest
{
ChallengeResponses = new Dictionary
{
{ GetChallengeParamCodeName(mfaRequest.ChallengeNameType), mfaRequest.MfaCode},
{ CognitoConstants.ChlgParamUsername, Username }
},
Session = mfaRequest.SessionID,
ClientId = ClientID,
ChallengeName = mfaRequest.ChallengeNameType
};
if (!string.IsNullOrEmpty(SecretHash))
{
challengeRequest.ChallengeResponses.Add(CognitoConstants.ChlgParamSecretHash, SecretHash);
}
RespondToAuthChallengeResponse challengeResponse =
await Provider.RespondToAuthChallengeAsync(challengeRequest, cancellationToken).ConfigureAwait(false);
UpdateSessionIfAuthenticationComplete(challengeResponse.ChallengeName, challengeResponse.AuthenticationResult);
return new AuthFlowResponse(challengeResponse.Session,
challengeResponse.AuthenticationResult,
challengeResponse.ChallengeName,
challengeResponse.ChallengeParameters,
new Dictionary(challengeResponse.ResponseMetadata.Metadata));
}
///
/// Internal method which works out which Challenge Parameter to use based on the ChallengeTypeName
///
/// ChallengeTypeName from the challenge
/// Returns the CognitoConstants for the given ChallengeTypeName
private string GetChallengeParamCodeName(ChallengeNameType challengeNameType )
{
if (challengeNameType == ChallengeNameType.SMS_MFA) return CognitoConstants.ChlgParamSmsMfaCode;
if (challengeNameType == ChallengeNameType.SOFTWARE_TOKEN_MFA) return CognitoConstants.ChlgParamSoftwareTokenMfaCode;
return null;
}
///
/// Uses the properties of the RespondToNewPasswordRequiredRequest object to respond to the current new
/// password required authentication challenge using an asynchronous call
///
/// RespondToNewPasswordRequiredRequest object containing the necessary
/// parameters to respond to the current SMS MFA authentication challenge
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual Task RespondToNewPasswordRequiredAsync(RespondToNewPasswordRequiredRequest newPasswordRequest)
{
return RespondToNewPasswordRequiredAsync(newPasswordRequest, null, default);
}
///
/// Uses the properties of the RespondToNewPasswordRequiredRequest object to respond to the current new
/// password required authentication challenge using an asynchronous call
///
/// RespondToNewPasswordRequiredRequest object containing the necessary
/// parameters to respond to the current SMS MFA authentication challenge
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual Task RespondToNewPasswordRequiredAsync(RespondToNewPasswordRequiredRequest newPasswordRequest, CancellationToken cancellationToken)
{
return RespondToNewPasswordRequiredAsync(newPasswordRequest, null, cancellationToken);
}
///
/// Uses the properties of the RespondToNewPasswordRequiredRequest object to respond to the current new
/// password required authentication challenge using an asynchronous call
///
/// RespondToNewPasswordRequiredRequest object containing the necessary
/// Optional dictionnary of attributes that may be required by the user pool
/// Each attribute key must be prefixed by "userAttributes."
/// parameters to respond to the current SMS MFA authentication challenge
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task RespondToNewPasswordRequiredAsync(RespondToNewPasswordRequiredRequest newPasswordRequest, Dictionary requiredAttributes)
{
return await RespondToNewPasswordRequiredAsync(newPasswordRequest, requiredAttributes, default);
}
///
/// Uses the properties of the RespondToNewPasswordRequiredRequest object to respond to the current new
/// password required authentication challenge using an asynchronous call
///
/// RespondToNewPasswordRequiredRequest object containing the necessary
/// Optional dictionnary of attributes that may be required by the user pool
/// Each attribute key must be prefixed by "userAttributes."
/// parameters to respond to the current SMS MFA authentication challenge
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task RespondToNewPasswordRequiredAsync(RespondToNewPasswordRequiredRequest newPasswordRequest, Dictionary requiredAttributes, CancellationToken cancellationToken)
{
var challengeResponses = new Dictionary()
{
{ CognitoConstants.ChlgParamNewPassword, newPasswordRequest.NewPassword},
{ CognitoConstants.ChlgParamUsername, Username }
};
if (requiredAttributes != null)
{
foreach (KeyValuePair attribute in requiredAttributes)
{
challengeResponses.Add(attribute.Key, attribute.Value);
}
}
RespondToAuthChallengeRequest challengeRequest = new RespondToAuthChallengeRequest
{
ChallengeResponses = challengeResponses,
Session = newPasswordRequest.SessionID,
ClientId = ClientID,
ChallengeName = ChallengeNameType.NEW_PASSWORD_REQUIRED
};
if (!string.IsNullOrEmpty(SecretHash))
{
challengeRequest.ChallengeResponses.Add(CognitoConstants.ChlgParamSecretHash, SecretHash);
}
RespondToAuthChallengeResponse challengeResponse =
await Provider.RespondToAuthChallengeAsync(challengeRequest, cancellationToken).ConfigureAwait(false);
UpdateSessionIfAuthenticationComplete(challengeResponse.ChallengeName, challengeResponse.AuthenticationResult);
return new AuthFlowResponse(challengeResponse.Session,
challengeResponse.AuthenticationResult,
challengeResponse.ChallengeName,
challengeResponse.ChallengeParameters,
new Dictionary(challengeResponse.ResponseMetadata.Metadata));
}
///
/// Initiates the asynchronous refresh token authentication flow
///
/// InitiateRefreshTokenAuthRequest object containing the necessary
/// parameters to initiate the refresh token authentication flow
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task StartWithRefreshTokenAuthAsync(InitiateRefreshTokenAuthRequest refreshTokenRequest)
{
return await StartWithRefreshTokenAuthAsync(refreshTokenRequest, default);
}
///
/// Initiates the asynchronous refresh token authentication flow
///
/// InitiateRefreshTokenAuthRequest object containing the necessary
/// parameters to initiate the refresh token authentication flow
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task StartWithRefreshTokenAuthAsync(InitiateRefreshTokenAuthRequest refreshTokenRequest, CancellationToken cancellationToken)
{
InitiateAuthRequest initiateAuthRequest = CreateRefreshTokenAuthRequest(refreshTokenRequest.AuthFlowType);
InitiateAuthResponse initiateResponse =
await Provider.InitiateAuthAsync(initiateAuthRequest, cancellationToken).ConfigureAwait(false);
// Service does not return the refresh token. Hence, set it to the old refresh token that was used.
if (string.IsNullOrEmpty(initiateResponse.ChallengeName) && string.IsNullOrEmpty(initiateResponse.AuthenticationResult.RefreshToken))
initiateResponse.AuthenticationResult.RefreshToken = initiateAuthRequest.AuthParameters[CognitoConstants.ChlgParamRefreshToken];
UpdateSessionIfAuthenticationComplete(initiateResponse.ChallengeName, initiateResponse.AuthenticationResult);
return new AuthFlowResponse(initiateResponse.Session,
initiateResponse.AuthenticationResult,
initiateResponse.ChallengeName,
initiateResponse.ChallengeParameters,
new Dictionary(initiateResponse.ResponseMetadata.Metadata));
}
///
/// Initiates the asynchronous ADMIN_NO_SRP_AUTH authentication flow
///
/// InitiateAdminNoSrpAuthRequest object containing the necessary
/// parameters to initiate the ADMIN_NO_SRP_AUTH authentication flow
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task StartWithAdminNoSrpAuthAsync(InitiateAdminNoSrpAuthRequest adminAuthRequest)
{
return await StartWithAdminNoSrpAuthAsync(adminAuthRequest, default);
}
///
/// Initiates the asynchronous ADMIN_NO_SRP_AUTH authentication flow
///
/// InitiateAdminNoSrpAuthRequest object containing the necessary
/// parameters to initiate the ADMIN_NO_SRP_AUTH authentication flow
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation
/// Returns the AuthFlowResponse object that can be used to respond to the next challenge,
/// if one exists
public virtual async Task StartWithAdminNoSrpAuthAsync(InitiateAdminNoSrpAuthRequest adminAuthRequest, CancellationToken cancellationToken)
{
AdminInitiateAuthRequest initiateAuthRequest = CreateAdminAuthRequest(adminAuthRequest);
AdminInitiateAuthResponse initiateResponse =
await Provider.AdminInitiateAuthAsync(initiateAuthRequest, cancellationToken).ConfigureAwait(false);
UpdateSessionIfAuthenticationComplete(initiateResponse.ChallengeName, initiateResponse.AuthenticationResult);
return new AuthFlowResponse(initiateResponse.Session,
initiateResponse.AuthenticationResult,
initiateResponse.ChallengeName,
initiateResponse.ChallengeParameters,
new Dictionary(initiateResponse.ResponseMetadata.Metadata));
}
///
/// Internal method for updating the CognitoUser SessionTokens property if properly authenticated
///
private void UpdateSessionIfAuthenticationComplete(ChallengeNameType challengeName, AuthenticationResultType authResult)
{
if (string.IsNullOrEmpty(challengeName))
{
CognitoUserSession cognitoUserSession = GetCognitoUserSession(authResult);
this.SessionTokens = cognitoUserSession;
}
}
///
/// Interal method which creates the InitiateAuthRequest for an SRP authentication flow
///
/// Tuple containing the A,a pair for SRP authentication
/// Returns the InitiateAuthRequest for an SRP authentication flow
private InitiateAuthRequest CreateSrpAuthRequest(Tuple tupleAa)
{
InitiateAuthRequest initiateAuthRequest = new InitiateAuthRequest()
{
AuthFlow = AuthFlowType.USER_SRP_AUTH,
ClientId = ClientID,
AuthParameters = new Dictionary(StringComparer.Ordinal)
{
{ CognitoConstants.ChlgParamUsername, Username },
{ CognitoConstants.ChlgParamSrpA, tupleAa.Item1.ToString("X") }
}
};
if (!string.IsNullOrEmpty(ClientSecret))
{
initiateAuthRequest.AuthParameters.Add(CognitoConstants.ChlgParamSecretHash,
CognitoAuthHelper.GetUserPoolSecretHash(Username, ClientID, ClientSecret));
}
if (Device != null && !string.IsNullOrEmpty(Device.DeviceKey))
{
initiateAuthRequest.AuthParameters.Add(CognitoConstants.ChlgParamDeviceKey, Device.DeviceKey);
}
return initiateAuthRequest;
}
///
/// Internal mehtod which updates CognitoUser's username, secret hash, and device key from challege parameters
///
/// Dictionary containing the key-value pairs for challenge parameters
private void UpdateUsernameAndSecretHash(IDictionary challengeParameters)
{
bool canSetUsername = string.IsNullOrEmpty(Username) || string.Equals(UserID, Username, StringComparison.Ordinal);
bool challengeParamIsUsername = challengeParameters != null && challengeParameters.ContainsKey(CognitoConstants.ChlgParamUsername);
bool shouldUpdate = canSetUsername || challengeParamIsUsername;
if (!shouldUpdate)
{
return;
}
if (challengeParameters.ContainsKey(CognitoConstants.ChlgParamUsername))
{
Username = challengeParameters[CognitoConstants.ChlgParamUsername];
}
if (!string.IsNullOrEmpty(ClientSecret))
{
SecretHash = CognitoAuthHelper.GetUserPoolSecretHash(Username, ClientID, ClientSecret);
}
}
private AdminInitiateAuthRequest CreateAdminAuthRequest(InitiateAdminNoSrpAuthRequest adminRequest)
{
AdminInitiateAuthRequest returnRequest = new AdminInitiateAuthRequest()
{
AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH,
ClientId = ClientID,
UserPoolId = UserPool.PoolID,
AuthParameters = new Dictionary()
{
{ CognitoConstants.ChlgParamUsername, Username },
{CognitoConstants.ChlgParamPassword, adminRequest.Password }
}
};
if (Device != null && !string.IsNullOrEmpty(Device.DeviceKey))
{
returnRequest.AuthParameters.Add(CognitoConstants.ChlgParamDeviceKey, Device.DeviceKey);
}
if (!string.IsNullOrEmpty(SecretHash))
{
returnRequest.AuthParameters.Add(CognitoConstants.ChlgParamSecretHash, SecretHash);
}
if (adminRequest.ClientMetadata != null)
{
returnRequest.ClientMetadata = new Dictionary(adminRequest.ClientMetadata);
}
return returnRequest;
}
private InitiateAuthRequest CreateRefreshTokenAuthRequest(AuthFlowType authFlowType)
{
if (authFlowType != AuthFlowType.REFRESH_TOKEN && authFlowType != AuthFlowType.REFRESH_TOKEN_AUTH)
{
throw new ArgumentException("authFlowType must be either \"REFRESH_TOKEN\" or \"REFRESH_TOKEN_AUTH\"", "authFlowType");
}
InitiateAuthRequest initiateAuthRequest = new InitiateAuthRequest()
{
AuthFlow = authFlowType,
ClientId = ClientID,
AuthParameters = new Dictionary()
{
{CognitoConstants.ChlgParamUsername, Username },
{CognitoConstants.ChlgParamRefreshToken, SessionTokens.RefreshToken }
}
};
if (Device != null && !string.IsNullOrEmpty(Device.DeviceKey))
{
initiateAuthRequest.AuthParameters.Add(CognitoConstants.ChlgParamDeviceKey, Device.DeviceKey);
}
if (!string.IsNullOrEmpty(SecretHash))
{
initiateAuthRequest.AuthParameters.Add(CognitoConstants.ChlgParamSecretHash, SecretHash);
}
return initiateAuthRequest;
}
///
/// Internal method which responds to the PASSWORD_VERIFIER challenge in SRP authentication
///
/// Response from the InitiateAuth challenge
/// Password for the CognitoUser, needed for authentication
/// Tuple of BigIntegers containing the A,a pair for the SRP protocol flow
/// Returns the RespondToAuthChallengeRequest for an SRP authentication flow
private RespondToAuthChallengeRequest CreateSrpPasswordVerifierAuthRequest(InitiateAuthResponse challenge,
string password,
Tuple tupleAa)
{
string username = challenge.ChallengeParameters[CognitoConstants.ChlgParamUsername];
string poolName = PoolName;
string secretBlock = challenge.ChallengeParameters[CognitoConstants.ChlgParamSecretBlock];
string salt = challenge.ChallengeParameters[CognitoConstants.ChlgParamSalt];
BigInteger srpb = BigIntegerExtensions.FromUnsignedLittleEndianHex(challenge.ChallengeParameters[CognitoConstants.ChlgParamSrpB]);
if ((srpb.TrueMod(AuthenticationHelper.N)).Equals(BigInteger.Zero))
{
throw new ArgumentException("SRP error, B mod N cannot be zero.", "challenge");
}
DateTime timestamp = DateTime.UtcNow;
string timeStr = timestamp.ToString("ddd MMM d HH:mm:ss \"UTC\" yyyy", CultureInfo.InvariantCulture);
byte[] claim = AuthenticationHelper.AuthenticateUser(username, password, poolName, tupleAa, salt,
challenge.ChallengeParameters[CognitoConstants.ChlgParamSrpB], secretBlock, timeStr);
string claimBase64 = Convert.ToBase64String(claim);
Dictionary srpAuthResponses = new Dictionary(StringComparer.Ordinal)
{
{CognitoConstants.ChlgParamPassSecretBlock, secretBlock},
{CognitoConstants.ChlgParamPassSignature, claimBase64},
{CognitoConstants.ChlgParamUsername, username },
{CognitoConstants.ChlgParamTimestamp, timeStr },
};
if (!string.IsNullOrEmpty(ClientSecret))
{
SecretHash = CognitoAuthHelper.GetUserPoolSecretHash(Username, ClientID, ClientSecret);
srpAuthResponses.Add(CognitoConstants.ChlgParamSecretHash, SecretHash);
}
if (Device != null && !string.IsNullOrEmpty(Device.DeviceKey))
{
srpAuthResponses.Add(CognitoConstants.ChlgParamDeviceKey, Device.DeviceKey);
}
RespondToAuthChallengeRequest authChallengeRequest = new RespondToAuthChallengeRequest()
{
ChallengeName = challenge.ChallengeName,
ClientId = ClientID,
Session = challenge.Session,
ChallengeResponses = srpAuthResponses
};
return authChallengeRequest;
}
///
/// Creates the CognitoAWSCredentials for accessing AWS resources. Should only be called with an authenticated user.
///
/// The poolID of the identity pool the user belongs to
/// The region of the identity pool the user belongs to
/// Returns the CognitoAWSCredentials for the user to be able to access AWS resources
public CognitoAWSCredentials GetCognitoAWSCredentials(string identityPoolID, RegionEndpoint identityPoolRegion)
{
EnsureUserAuthenticated();
string poolRegion = UserPool.PoolID.Substring(0, UserPool.PoolID.IndexOf("_"));
string providerName = "cognito-idp." + poolRegion + ".amazonaws.com/" + UserPool.PoolID;
CognitoAWSCredentials credentials = new CognitoAWSCredentials(identityPoolID, identityPoolRegion);
credentials.AddLogin(providerName, SessionTokens.IdToken);
return credentials;
}
}
}