aws / aws-sdk-net-extensions-cognito

An extension library to assist in the Amazon Cognito User Pools authentication process
Apache License 2.0
102 stars 49 forks source link

Error assigning valid device_key to a CognitoDevice and applying it to CongitoUser.Device prior to calling StartWithSrpAuthAsync (Device Tracking Enabled) #44

Closed dcolclazier closed 3 years ago

dcolclazier commented 4 years ago

I can't seem to figure out how to properly add a newly tracked device to a CognitoUser.Device prior to logging in, so the login is tracked for that device key.

Here is the general flow, as an overview:

2: The AuthenticationResult object within the response contains a NewDeviceMetadata object, which contains a new DeviceKey (CONFIRMED)

3: The application generates a salt and password verifier and passes it to Cognito, and receives a 200 response.

var confirmDeviceResponse = await ConfirmDeviceAsync(creds, new NewDeviceInfo
{
    GroupKey = authResponse.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey,
    Key = authResponse.AuthenticationResult.NewDeviceMetadata.DeviceKey,
    Name = _settings.DeviceName
});

if (confirmDeviceResponse.HttpStatusCode != HttpStatusCode.OK)
{
    throw new Exception(JObject.FromObject(confirmDeviceResponse).ToString());
}
  1. First we save the now-tracked deviceKey to user settings for the application. As a test, we then try immediately refreshing the Cognito session, leveraging InitiateAuthAsync with a DEVICE_KEY AuthParameter and a REFRESH_TOKEN_AUTH AuthFlow:

var userPool = new CognitoUserPool(currentCreds.UserPoolId, currentCreds.ClientId, _cognitoClient);
var user = new CognitoUser(currentCreds.Username, currentCreds.ClientId, userPool, _cognitoClient)
{
     //we have to do this its a bug in the 'StartWithRefreshTokenAuthAsync' 
     //see article https://github.com/aws/aws-sdk-net/issues/844
     SessionTokens = new CognitoUserSession(
         currentCreds.UserPoolIdToken,
         currentCreds.UserPoolAccessToken,
         currentCreds.UserPoolRefreshToken,
         DateTime.Now,
         DateTime.Now.AddHours(1)
     )
};
 user.Device = new CognitoDevice(
     new DeviceType { DeviceKey = currentCreds.DeviceKey }, 
     user
);

var authResponse = await _cognitoClient.InitiateAuthAsync(new InitiateAuthRequest()
{
      AuthFlow = AuthFlowType.REFRESH_TOKEN_AUTH,
      ClientId = currentCreds.ClientId,
      AuthParameters = new Dictionary<string, string>()
      {
          { "USERNAME", currentCreds.Username },
          { "REFRESH_TOKEN", currentCreds.UserPoolRefreshToken },
          { "DEVICE_KEY",  currentCreds.DeviceKey }
       }
});

var newCreds = user.GetCognitoAWSCredentials(
       currentCreds.IdentityPoolId,
       Amazon.RegionEndpoint.USEast1
).GetCredentials();

5: Now, the next time we load the application, we use the device key that now exists:

var userPool = new CognitoUserPool(_settings.UserPoolId, _settings.ClientId, _cognitoClient);
var user = new CognitoUser(userName, _settings.ClientId, userPool, _cognitoClient);

//use existing device key if available
if (_settings.DeviceKey != string.Empty)
{
    var device = new CognitoDevice(new DeviceType
    {
        DeviceKey = _settings.DeviceKey
    }, user);
    user.Device = device;
}
var authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
{
    Password = password
}).ConfigureAwait(false);

6: Observe error during StartWithSrpAuthAsync() from step 5:

Unhandled Exception: System.ArgumentException: An item with the same key has already been added. Key: DEVICE_KEY
   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
   at Amazon.Extensions.CognitoAuthentication.CognitoUser.StartWithSrpAuthAsync(InitiateSrpAuthRequest srpRequest)

I've also tried calling the following after authenticating (for step 5), but receive the same error:

await user.Device.RememberThisDeviceAsync(); 
var authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
{
    Password = password
}).ConfigureAwait(false);
//use existing device key if available
if (_settings.DeviceKey != string.Empty)
{
    var device = new CognitoDevice(new DeviceType
    {
        DeviceKey = _settings.DeviceKey
    }, user);
    user.Device = device;
    await user.Device.RememberThisDeviceAsync();
}
ThomasJaeger commented 4 years ago

Any update on this?

ThomasJaeger commented 4 years ago

@dcolclazier In your step 3 above, how do you set creds? Also, I do not see an overloaded constructor for ConfirmDeviceAsyncto pass credsand NewDeviceInfo. The ConfirmDeviceRequestclass does not have credsnor NewDeviceInfo.

dcolclazier commented 4 years ago

Hey Thomas,

Thanks for looking into this! Our "creds" object is as follows:

var userDetailsObj = await user.GetUserDetailsAsync();
var userCreds = user.GetCognitoAWSCredentials(_settings.IdentityPoolId, Amazon.RegionEndpoint.USEast1).GetCredentials();
var creds = new Credentials
{
    Region = Amazon.RegionEndpoint.USEast1.ToString(),
    Username = userName,
    UserEmail = userDetailsObj.UserAttributes.First(a => a.Name == "email").Value,
    ExternalId = userDetailsObj.UserAttributes.First(a => a.Name == "custom:systm_identity").Value,
    IssuedOn = DateTime.Now,
    UserPoolId = _settings.UserPoolId,
    IdentityPoolId = _settings.IdentityPoolId,
    ClientId = _settings.ClientId,
    ExpireTimeString = DateTime.UtcNow.AddSeconds(authResponse.AuthenticationResult.ExpiresIn).ToString(),
    TemporaryIdentityPoolToken = userCreds.Token,
    TemporaryIAMAccessKey = userCreds.AccessKey,
    TemporaryIAMSecretKey = userCreds.SecretKey,
    UserPoolIdToken = authResponse.AuthenticationResult.IdToken,
    UserPoolRefreshToken = authResponse.AuthenticationResult.RefreshToken,
    UserPoolAccessToken = authResponse.AuthenticationResult.AccessToken,
    DeviceKey = user.Device?.DeviceKey ?? string.Empty
};

In addition, here is the ConfirmDeviceAsync logic

public async Task<ConfirmDeviceResponse> ConfirmDeviceAsync(Credentials credentials, NewDeviceInfo newDeviceInfo)
{
    try
    {
        var authenticationHelper = new AuthenticationHelper();

        _settings.DeviceVerifier = authenticationHelper.GenerateHashDevice(newDeviceInfo.GroupKey, newDeviceInfo.Key);
        _settings.Save();
        _settings.Reload();

        var confirmDeviceRequest = new ConfirmDeviceRequest
        {
            DeviceKey = newDeviceInfo.Key,
            AccessToken = credentials.UserPoolAccessToken,
            DeviceName = newDeviceInfo.Name,
            DeviceSecretVerifierConfig = new DeviceSecretVerifierConfigType()
            {
                PasswordVerifier = Convert.ToBase64String(authenticationHelper.GetVerifierBytes()),
                Salt = Convert.ToBase64String(authenticationHelper.GetSaltBytes())
            }
        };

        var response = await _cognitoClient.ConfirmDeviceAsync(confirmDeviceRequest);

        return response;
    }
    catch (Exception error)
    {
        string errorMessage = " - ERROR in Confirm Device - " + Environment.NewLine;
        if (error.Data.Contains("CogntioFail"))
        {
            error.Data["CogntioFail"] += errorMessage;
        }
        else
        {
            error.Data.Add("CogntioFail", errorMessage);
        }
        throw error;
    }
}

Let me know if you need our implementation of AuthenticationHelper - I can confirm we can get a successful response from

var response = await _cognitoClient.ConfirmDeviceAsync(confirmDeviceRequest);
dcolclazier commented 4 years ago

I should mention - the original issue is caused by how the device key is added to the ChallengeResponse - see the following line of code in CognitoUserAuthentication.cs

if (challengeResponsesValid && deviceKeyValid)
{
    challengeRequest.ChallengeResponses.Add(CognitoConstants.ChlgParamDeviceKey, Device.DeviceKey);
}

should be

if (challengeResponsesValid && deviceKeyValid)
{
    challengeRequest.ChallengeResponses[CognitoConstants.ChlgParamDeviceKey] = Device.DeviceKey;
}

Also, if a device key is provided during StartWithSrpAuthAsync, an additional "DEVICE_SRP_AUTH" challenge request comes back from Cognito after the initial (successful) challenge request, which isn't being handled by your SDK.

dcolclazier commented 4 years ago

I've made a bit of progress on this; maybe you can help me out:

The authFlow for StartWithSrpAuthAsync if a Device Key is added to the user is as follows:

SRP_AUTH PASSWORD_VERIFIER DEVICE_SRP_AUTH DEVICE_PASSWORD_VERIFIER

I have added the necessary functionality to make it past the third flow, but then hit a snag on DEVICE_PASSWORD_VERIFIER (incessant Invalid User or Password errors).

First, salt & v generation, valid when confirming the new device prior to this auth flow (inside Amazon.Extensions.CognitoAuthentication.AuthenticationHelper):

public static Tuple<string, string, string> GenerateDeviceVerifier(string deviceGroupKey, string deviceKey)
{
    var randomPass = Convert.ToBase64String(RandomBytes(40));

    byte[] userIdContent = CognitoAuthHelper.CombineBytes(new byte[][] { 
        Encoding.UTF8.GetBytes(deviceGroupKey), 
        Encoding.UTF8.GetBytes(deviceKey),
        Encoding.UTF8.GetBytes(":"), 
        Encoding.UTF8.GetBytes(randomPass)
    });

    byte[] userIdHash = CognitoAuthHelper.Sha256.ComputeHash(userIdContent);
    var salt = BigInteger.Parse(BitConverter.ToString(RandomBytes(16)).Replace("-", string.Empty), NumberStyles.HexNumber);
    var saltBytes = salt.ToBigEndianByteArray();

    byte[] xBytes = CognitoAuthHelper.CombineBytes(new byte[][] { saltBytes, userIdHash });
    byte[] xDigest = CognitoAuthHelper.Sha256.ComputeHash(xBytes);
    BigInteger x = BigIntegerExtensions.FromUnsignedBigEndian(xDigest);

    var v = BigInteger.ModPow(g, x, N);
    var vBytes = v.ToBigEndianByteArray();

    return Tuple.Create(randomPass, Convert.ToBase64String(vBytes), Convert.ToBase64String(saltBytes));
}
private static byte[] RandomBytes(int size)
{
    var bytes = new byte[size];
    new Random().NextBytes(bytes);
    return bytes;
}

Then, CognitoUserAuthentication.StartWithSrpAuthAsync():

/// <summary>
/// Initiates the asynchronous SRP authentication flow
/// </summary>
/// <param name="srpRequest">InitiateSrpAuthRequest object containing the necessary parameters to
/// create an InitiateAuthAsync API call for SRP authentication</param>
/// <returns>Returns the AuthFlowResponse object that can be used to respond to the next challenge, 
/// if one exists</returns>
public async Task<AuthFlowResponse> StartWithSrpAuthAsync(InitiateSrpAuthRequest srpRequest)
{
    #region User SRP Auth
    if (srpRequest == null || string.IsNullOrEmpty(srpRequest.Password))
    {
        throw new ArgumentNullException("Password required for authentication.", "srpRequest");
    }

    Tuple<BigInteger, BigInteger> tupleAa = AuthenticationHelper.CreateAaTuple();
    InitiateAuthRequest initiateRequest = CreateSrpAuthRequest(tupleAa);

    InitiateAuthResponse initiateResponse = await Provider.InitiateAuthAsync(initiateRequest).ConfigureAwait(false);
    UpdateUsernameAndSecretHash(initiateResponse.ChallengeParameters); 
    #endregion

    #region User Password Verifier
    RespondToAuthChallengeRequest challengeRequest =
            CreateSrpPasswordVerifierAuthRequest(initiateResponse, srpRequest.Password, tupleAa);

    bool challengeResponsesValid = challengeRequest != null && challengeRequest.ChallengeResponses != null;
    bool deviceKeyValid = Device != null && !string.IsNullOrEmpty(Device.DeviceKey);

    if (challengeResponsesValid && deviceKeyValid)
    {
        challengeRequest.ChallengeResponses[CognitoConstants.ChlgParamDeviceKey] = Device.DeviceKey;
    }

    var verifierResponse = await Provider.RespondToAuthChallengeAsync(challengeRequest).ConfigureAwait(false);
    #endregion

    //this response comes back with a "DEVICE_SRP_AUTH" challenge, because there is a Device attached to the user.
    //DEVICE_SRP_AUTH requires USERNAME, DEVICE_KEY, SRP_A (and SECRET_HASH). 
    //DEVICE_PASSWORD_VERIFIER requires PASSWORD_CLAIM_SIGNATURE , PASSWORD_CLAIM_SECRET_BLOCK , TIMESTAMP , USERNAME, and DEVICE_KEY .

    #region Device SRP Auth
    if(verifierResponse.AuthenticationResult == null)
    {
        if (verifierResponse == null || string.IsNullOrEmpty(srpRequest.DeviceGroupKey) || string.IsNullOrEmpty(srpRequest.DevicePass))
        {
            throw new ArgumentNullException("Device Group Key and Device Pass required for authentication.", "srpRequest");
        }

        var deviceAuthRequest = CreateDeviceSrpAuthRequest(tupleAa);
        var deviceAuthResponse = await RespondToDeviceSrpAuthAsync(deviceAuthRequest).ConfigureAwait(false);
        UpdateUsernameAndSecretHash(deviceAuthResponse.ChallengeParameters);

        #region Device Password Verifier
        var devicePasswordChallengeRequest = CreateDevicePasswordVerifierAuthRequest(deviceAuthResponse, srpRequest.DeviceGroupKey, srpRequest.DevicePass, tupleAa);
        verifierResponse = await Provider.RespondToAuthChallengeAsync(devicePasswordChallengeRequest).ConfigureAwait(false);
        #endregion

    }
    #endregion
    UpdateSessionIfAuthenticationComplete(verifierResponse.ChallengeName, verifierResponse.AuthenticationResult);

    return new AuthFlowResponse(verifierResponse.Session,
        verifierResponse.AuthenticationResult,
        verifierResponse.ChallengeName,
        verifierResponse.ChallengeParameters,
        new Dictionary<string, string>(verifierResponse.ResponseMetadata.Metadata));

}

During this, we create the DevicePasswordVerifierRequest:

/// <summary>
/// Internal method which responds to the DEVICE_PASSWORD_VERIFIER challenge in SRP authentication
/// </summary>
/// <param name="challenge">Response from the InitiateAuth challenge</param>
/// <param name="devicePassword">Password for the CognitoDevice, needed for authentication</param>
/// <param name="deviceKeyGroup">Group Key for the CognitoDevice, needed for authentication</param>
/// <param name="tupleAa">Tuple of BigIntegers containing the A,a pair for the SRP protocol flow</param>
/// <returns>Returns the RespondToAuthChallengeRequest for an SRP authentication flow</returns>
private RespondToAuthChallengeRequest CreateDevicePasswordVerifierAuthRequest(AuthFlowResponse challenge,
                                                                           string deviceKeyGroup,
                                                                           string devicePassword,
                                                                           Tuple<BigInteger, BigInteger> 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");
    }

    DateTime timestamp = DateTime.UtcNow;
    string timeStr = timestamp.ToString("ddd MMM d HH:mm:ss \"UTC\" yyyy", CultureInfo.InvariantCulture);

    var claimBytes = AuthenticationHelper.AuthenticateDevice(deviceKey, devicePassword, deviceKeyGroup, salt,  
        challenge.ChallengeParameters[CognitoConstants.ChlgParamSrpB], secretBlock, timeStr, tupleAa);

    string claimB64 = Convert.ToBase64String(claimBytes);
    Dictionary<string, string> srpAuthResponses = new Dictionary<string, string>(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.SessionID,
        ChallengeResponses = srpAuthResponses
    };

    return authChallengeRequest;
}

Which requires creating the PASSWORD_CLAIM_SIGNATURE in AuthenticationHelper.AuthenticateDevice (equivalent to AuthenticationHelper.AuthenticateUser, but uses deviceKey, deviceGroupKey, and devicePass instead of normal user, pass, and userpool) :

/// <summary>
/// Generates the claim for authenticating a device through the SRP protocol
/// </summary>
/// <param name="deviceKey"> Key of CognitoDevice</param>
/// <param name="devicePassword"> Password of CognitoDevice</param>
/// <param name="deviceGroupKey"> GroupKey of CognitoDevice</param>
/// <param name="tupleAa"> TupleAa from CreateAaTuple</param>
/// <param name="saltString"> salt provided in ChallengeParameters from Cognito </param>
/// <param name="srpbString"> srpb provided in ChallengeParameters from Cognito</param>
/// <param name="secretBlockBase64">secret block provided in ChallengeParameters from Cognito</param>
/// <param name="formattedTimestamp">En-US Culture of Current Time</param>
/// <returns>Returns the claim for authenticating the given user</returns>
public static byte[] AuthenticateDevice(
    string deviceKey,
    string devicePassword,
    string deviceGroupKey,
    string saltString,
    string srpbString,
    string secretBlockBase64,
    string formattedTimestamp,
    Tuple<BigInteger, BigInteger> tupleAa)

{
    var B = BigIntegerExtensions.FromUnsignedLittleEndianHex(srpbString);
    if (B.TrueMod(N).Equals(BigInteger.Zero)) throw new ArgumentException("B mod N cannot be zero.", nameof(srpbString));

    var salt = BigIntegerExtensions.FromUnsignedLittleEndianHex(saltString);
    var secretBlockBytes = Convert.FromBase64String(secretBlockBase64);
    // Need to generate the key to hash the response based on our A and what AWS sent back
    var key = GetDeviceAuthenticationKey(deviceKey, devicePassword, deviceGroupKey, tupleAa, B, salt);

    // HMAC our data with key (HKDF(S)) (the shared secret)
    var msg = CognitoAuthHelper.CombineBytes(new[] {
        Encoding.UTF8.GetBytes(deviceGroupKey),
        Encoding.UTF8.GetBytes(deviceKey),
        secretBlockBytes,
        Encoding.UTF8.GetBytes(formattedTimestamp)
    });

    using (var hashAlgorithm = new HMACSHA256(key))
    {
        return hashAlgorithm.ComputeHash(msg);
    }
}

The "key" above is generated from the following:

/// <summary>
/// Creates the Device Password Authentication Key based on the SRP protocol
/// </summary>
/// <param name="deviceKey"> Username of CognitoDevice</param>
/// <param name="devicePass">Password of CognitoDevice</param>
/// <param name="deviceGroup">GroupKey of CognitoDevice</param>
/// <param name="Aa">Returned from TupleAa</param>
/// <param name="B">BigInteger SRPB from AWS ChallengeParameters</param>
/// <param name="salt">BigInteger salt from AWS ChallengeParameters</param>
/// <returns>Returns the password authentication key for the SRP protocol</returns>
public static byte[] GetDeviceAuthenticationKey(string deviceKey,
    string devicePass,
    string deviceGroup,
    Tuple<BigInteger, BigInteger> Aa,
    BigInteger B,
    BigInteger salt)
{
    // Authenticate the password
    // u = H(A, B)
    byte[] contentBytes = CognitoAuthHelper.CombineBytes(new[] { Aa.Item1.ToBigEndianByteArray(), B.ToBigEndianByteArray() });
    byte[] digest = CognitoAuthHelper.Sha256.ComputeHash(contentBytes);

    BigInteger u = BigIntegerExtensions.FromUnsignedBigEndian(digest);
    if (u.Equals(BigInteger.Zero))
    {
        throw new ArgumentException("Hash of A and B cannot be zero.");
    }

    // x = H(salt | H(deviceGroupKey | deviceKey | ":" | devicePassword))
    byte[] deviceContent = CognitoAuthHelper.CombineBytes(new byte[][] { Encoding.UTF8.GetBytes(deviceGroup), Encoding.UTF8.GetBytes(deviceKey),
                                        Encoding.UTF8.GetBytes(":"), Encoding.UTF8.GetBytes(devicePass)});
    byte[] deviceHash = CognitoAuthHelper.Sha256.ComputeHash(deviceContent);
    byte[] xBytes = CognitoAuthHelper.CombineBytes(new byte[][] { salt.ToBigEndianByteArray(), deviceHash });

    byte[] xDigest = CognitoAuthHelper.Sha256.ComputeHash(xBytes);
    BigInteger x = BigIntegerExtensions.FromUnsignedBigEndian(xDigest);

    var gModPowXn = BigInteger.ModPow(g, x, N);
    // Use HKDF to get final password authentication key
    var intValue2 = (B - k * gModPowXn).TrueMod(N);
    var s_value = BigInteger.ModPow(intValue2, Aa.Item2 + u * x, N);

    HkdfSha256 hkdfSha256 = new HkdfSha256(u.ToBigEndianByteArray(), s_value.ToBigEndianByteArray());
    return hkdfSha256.Expand(Encoding.UTF8.GetBytes(DerivedKeyInfo), DerivedKeySizeBytes);
}

This ultimately results in a invalid user/password response when calling this code (shown above):

        #region Device Password Verifier
        var devicePasswordChallengeRequest = CreateDevicePasswordVerifierAuthRequest(deviceAuthResponse, srpRequest.DeviceGroupKey, srpRequest.DevicePass, tupleAa);
        verifierResponse = await Provider.RespondToAuthChallengeAsync(devicePasswordChallengeRequest).ConfigureAwait(false);
        #endregion

Let me know if you need the DEVICE_SRP_AUTH code - I assume my issue lies somewhere in a)how I'm generating the original salt and passwordVerifier (although this doesn't return any errors when confirming the device), or how I'm generating the claim signature to pass back to DEVICE_PASSWORD_VERIFIER. The style of all of this code is pretty close to the existing logic, so it should be relatively easy for you guys to follow.

dcolclazier commented 4 years ago

Looks like the code on my is working now! Let me know if you want me to clean it up and create a pull request.

assyadh commented 4 years ago

Hi @dcolclazier, I would gladly take a pull request on that.

dcolclazier commented 4 years ago

Here you go:

https://github.com/aws/aws-sdk-net-extensions-cognito/pull/46

stephan1994w commented 4 years ago

Any news on this issue?

ashishdhingra commented 3 years ago

Hi @dcolclazier,

The fix should have been implemented in Amazon.Extensions.CognitoAuthentication 2.0.3. Could you please verify and confirm?

Thanks, Ashish

ashishdhingra commented 3 years ago

This issue has not recieved a response in 2 weeks. If you want to keep this issue open, please just leave a comment below and auto-close will be canceled.

RyanWarwick commented 3 years ago

Hi ,

I am on v2.2.1 but seem to be running into the NotAuthorizedException: Incorrect username or password issue.

    user = new CognitoUser(username, clientId, userPool, provider, clientSecret);
    CognitoDevice device = new CognitoDevice(new Amazon.CognitoIdentityProvider.Model.DeviceType()
    {
        DeviceKey = "ap-southeast-1_abcd1234"
    }, user);
    user.Device = device;
    InitiateSrpAuthRequest authRequest = new InitiateSrpAuthRequest()
    {
        Password = password,
        DeviceGroupKey = "-CCPW2e2I",
        DevicePass = "GW4nsddDCY9hHNObanWWMj1BbbDsoV8eThJWue+tjW6cnt/gdiHMgw==",
    };

    authResponse = user.StartWithSrpAuthAsync(authRequest).Result;

The device(s) are already set to Remember in the Cognito console. Please advise, thanks! :)

RyanWarwick commented 3 years ago

Hi ,

I am on v2.2.1 but seem to be running into the NotAuthorizedException: Incorrect username or password issue.

    user = new CognitoUser(username, clientId, userPool, provider, clientSecret);
    CognitoDevice device = new CognitoDevice(new Amazon.CognitoIdentityProvider.Model.DeviceType()
    {
        DeviceKey = "ap-southeast-1_abcd1234"
    }, user);
    user.Device = device;
    InitiateSrpAuthRequest authRequest = new InitiateSrpAuthRequest()
    {
        Password = password,
        DeviceGroupKey = "-CCPW2e2I",
        DevicePass = "GW4nsddDCY9hHNObanWWMj1BbbDsoV8eThJWue+tjW6cnt/gdiHMgw==",
    };

    authResponse = user.StartWithSrpAuthAsync(authRequest).Result;

The device(s) are already set to Remember in the Cognito console. Please advise, thanks! :)

Hi ,

I've managed to solve the issue. The solution is to

  1. Use deviceKey instead of Username when calling user.GenerateDeviceVerifier()

and then

  1. Use deviceKey instead of Username when calling GetDeviceAuthenticationKey in AuthenticationHelper.AuthenticateDevice()

I will create a separate issue to track this :)

mads195 commented 2 years ago

Word of caution to those stumbling across this (closed) issue - there are threads on SO and another issue here: https://github.com/aws/aws-sdk-net/issues/1054 that all question the implementation and/or documentation related to including device authentication within the user auth flow via the .net SDK.

There are references to a helper called AuthenticationHelper(.cs) but it is not particularly clear where or what this is. It doesn't appear to be in the SDK or the extensions package and may or may not be required.

I think the fundamental problem with the device authentication with regards to the .net SDK, is a lack of documentation. I am making the assumption of course that it does actually work!

alfarok commented 5 months ago

@RyanWarwick I am getting the same error as you. I tried swapping the userId for deviceKey but still get the Incorrect username or password. error you were also seeing. I outlined my issue here, #139. Do you have any suggestions or an example I could reference for comparison? Thanks!