Open renatoargh opened 3 months ago
Hey @renatoargh good to see you again!
I feel like this should be possible by extending the library. I'll see if I can prototype a solution. I'm in the middle of moving house right now, but I should be able to carve out some time to do this soon
Thank you a lot, Simon! Take your time and no rush.. please finish moving houses before paying attention to this! I would love trying to contribute a PR but all seems super cryptic to me. And if you enable sponsoring on this repo I will make sure to send you a few π» !
I've pushed a branch that's WIP
Still trying to figure out the response to DEVICE_PASSWORD_VERIFIER
. Getting NotAuthorizedException: Incorrect username or password.
so I must've done something wrong in one of the steps? I'll keep looking into it this week
Here's the code I'm using to prototype the fix. Should be able to run it on the new branch. It'll create a new user each time, auto-confirm them, setup MFA, remember their device, etc.
const {
createSrpSession,
signSrpSession,
wrapAuthChallenge,
wrapInitiateAuth,
createSecretHash,
createDeviceVerifier,
signSrpSessionWithDevice,
} = require("../cognito-srp-helper");
const {
CognitoIdentityProviderClient,
InitiateAuthCommand,
RespondToAuthChallengeCommand,
AssociateSoftwareTokenCommand,
SetUserMFAPreferenceCommand,
VerifySoftwareTokenCommand,
SignUpCommand,
ConfirmDeviceCommand,
UpdateDeviceStatusCommand,
} = require("@aws-sdk/client-cognito-identity-provider");
const { TOTP } = require("totp-generator");
const { faker } = require("@faker-js/faker");
const wait = async (time) => new Promise((resolve) => setTimeout(resolve, time - new Date().getTime()));
(async () => {
// ---------- Setup credentials for new user ----------
const username = faker.internet.userName();
const password = "Qwerty1!";
const poolId = "eu-west-2_ebRTcgfiK";
const clientId = "1eci0qkm70jpfov0uo2j1ejep";
const secretId = "1op7af116gm42riug0brsfku3fr1tl1jn5f54lernp5q1d5mksbv";
const cognitoIdentityProviderClient = new CognitoIdentityProviderClient({
region: "eu-west-2",
});
console.log("credentials:");
console.log({ username, password });
// ---------- Signup with new user ----------
const secretHash = createSecretHash(username, clientId, secretId);
const signupRes = await cognitoIdentityProviderClient.send(
// There's a pre-signup trigger to auto-confirm new users, so no need to Confirm post signup
new SignUpCommand({
ClientId: clientId,
Username: username,
Password: password,
SecretHash: secretHash,
}),
);
console.log("signupRes:");
console.log(signupRes);
// ---------- Signin 1. initiate signin attempt ----------
const srpSession1 = createSrpSession(username, password, poolId, false);
const initiateAuthRes1 = await cognitoIdentityProviderClient
.send(
new InitiateAuthCommand(
wrapInitiateAuth(srpSession1, {
ClientId: clientId,
AuthFlow: "USER_SRP_AUTH",
AuthParameters: {
CHALLENGE_NAME: "SRP_A",
SECRET_HASH: secretHash,
USERNAME: username,
},
}),
),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("initiateAuthRes1:");
console.log(initiateAuthRes1);
// ---------- Signin 1. respond to PASSWORD_VERIFIER challenge ----------
const signedSrpSession1 = signSrpSession(srpSession1, initiateAuthRes1);
const respondToAuthChallengeRes1a = await cognitoIdentityProviderClient
.send(
new RespondToAuthChallengeCommand(
wrapAuthChallenge(signedSrpSession1, {
ClientId: clientId,
ChallengeName: "PASSWORD_VERIFIER",
ChallengeResponses: {
SECRET_HASH: secretHash,
USERNAME: username,
},
}),
),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("respondToAuthChallengeRes1a:");
console.log(respondToAuthChallengeRes1a);
// ---------- Associate a TOTP token with the user ----------
const { AccessToken } = respondToAuthChallengeRes1a.AuthenticationResult;
const associateSoftwareTokenRes = await cognitoIdentityProviderClient
.send(
new AssociateSoftwareTokenCommand({
AccessToken,
}),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("associateSoftwareTokenRes:");
console.log(associateSoftwareTokenRes);
// ---------- Verify the TOTP token with the user ----------
const { SecretCode } = associateSoftwareTokenRes;
const { otp: otp1, expires: expires1 } = TOTP.generate(SecretCode);
const verifySoftwareTokenRes = await cognitoIdentityProviderClient
.send(
new VerifySoftwareTokenCommand({
AccessToken,
UserCode: otp1,
}),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("verifySoftwareTokenRes:");
console.log(verifySoftwareTokenRes);
// ---------- Set MFA preference to TOTP ----------
const setUserMFAPreferenceRes = await cognitoIdentityProviderClient
.send(
new SetUserMFAPreferenceCommand({
AccessToken,
SoftwareTokenMfaSettings: {
// won't work unless we associate and verify TOTP token with user
Enabled: true,
PreferredMfa: true,
},
}),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("setUserMFAPreferenceRes:");
console.log(setUserMFAPreferenceRes);
// ---------- Wait for a new OTP to generate ----------
console.log("waiting for new OTP . . .");
await wait(expires1);
// ---------- Signin 2. initiate signin attempt ----------
const srpSession2 = createSrpSession(username, password, poolId, false);
const initiateAuthRes2 = await cognitoIdentityProviderClient
.send(
new InitiateAuthCommand(
wrapInitiateAuth(srpSession2, {
ClientId: clientId,
AuthFlow: "USER_SRP_AUTH",
AuthParameters: {
CHALLENGE_NAME: "SRP_A",
SECRET_HASH: secretHash,
USERNAME: username,
},
}),
),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("initiateAuthRes2:");
console.log(initiateAuthRes2);
// ---------- Signin 2. respond to PASSWORD_VERIFIER challenge ----------
const signedSrpSession2 = signSrpSession(srpSession2, initiateAuthRes2);
const respondToAuthChallengeRes2a = await cognitoIdentityProviderClient
.send(
new RespondToAuthChallengeCommand(
wrapAuthChallenge(signedSrpSession2, {
ClientId: clientId,
ChallengeName: "PASSWORD_VERIFIER",
ChallengeResponses: {
SECRET_HASH: secretHash,
USERNAME: username,
},
}),
),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("respondToAuthChallengeRes2a:");
console.log(respondToAuthChallengeRes2a);
// ---------- Signin 2. respond to SOFTWARE_TOKEN_MFA challenge ----------
const { otp: otp2 } = TOTP.generate(SecretCode);
const { Session: Session2a } = respondToAuthChallengeRes2a;
const respondToAuthChallengeRes2b = await cognitoIdentityProviderClient
.send(
new RespondToAuthChallengeCommand(
wrapAuthChallenge(signedSrpSession2, {
ClientId: clientId,
ChallengeName: "SOFTWARE_TOKEN_MFA",
ChallengeResponses: {
SECRET_HASH: secretHash,
SOFTWARE_TOKEN_MFA_CODE: otp2,
USERNAME: username,
},
Session: Session2a,
}),
),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("respondToAuthChallengeRes2b:");
console.log(respondToAuthChallengeRes2b);
// ---------- Confirm the device (for tracking) ----------
const { DeviceGroupKey, DeviceKey } = respondToAuthChallengeRes2b.AuthenticationResult.NewDeviceMetadata;
const DeviceSecretVerifierConfig = createDeviceVerifier(username, DeviceGroupKey);
const confirmDeviceRes = await cognitoIdentityProviderClient
.send(
new ConfirmDeviceCommand({
AccessToken,
DeviceKey,
DeviceName: "example-friendly-name", // usually this is set a User-Agent
DeviceSecretVerifierConfig,
}),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("confirmDeviceRes:");
console.log(confirmDeviceRes);
// ---------- Remember the device (for easier logins) ----------
const updateDeviceStatusRes = await cognitoIdentityProviderClient
.send(
new UpdateDeviceStatusCommand({
AccessToken,
DeviceKey,
DeviceRememberedStatus: "remembered",
}),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("updateDeviceStatusRes:");
console.log(updateDeviceStatusRes);
// ---------- Signin 3. initiate signin attempt ----------
const srpSession3 = createSrpSession(username, password, poolId, false);
const initiateAuthRes3 = await cognitoIdentityProviderClient
.send(
new InitiateAuthCommand(
wrapInitiateAuth(srpSession3, {
ClientId: clientId,
AuthFlow: "USER_SRP_AUTH",
AuthParameters: {
CHALLENGE_NAME: "SRP_A",
SECRET_HASH: secretHash,
USERNAME: username,
DEVICE_KEY: DeviceKey,
},
}),
),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("initiateAuthRes3:");
console.log(initiateAuthRes3);
// ---------- Signin 3. respond to PASSWORD_VERIFIER challenge ----------
const signedSrpSession3 = signSrpSession(srpSession3, initiateAuthRes3);
const respondToAuthChallengeRes3a = await cognitoIdentityProviderClient
.send(
new RespondToAuthChallengeCommand(
wrapAuthChallenge(signedSrpSession3, {
ClientId: clientId,
ChallengeName: "PASSWORD_VERIFIER",
ChallengeResponses: {
SECRET_HASH: secretHash,
USERNAME: username,
DEVICE_KEY: DeviceKey,
},
}),
),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("respondToAuthChallengeRes3a:");
console.log(respondToAuthChallengeRes3a);
// ---------- Signin 3. respond to DEVICE_SRP_AUTH challenge ----------
const respondToAuthChallengeRes3b = await cognitoIdentityProviderClient
.send(
new RespondToAuthChallengeCommand(
wrapAuthChallenge(signedSrpSession3, {
ClientId: clientId,
ChallengeName: "DEVICE_SRP_AUTH",
ChallengeResponses: {
SECRET_HASH: secretHash,
USERNAME: username,
DEVICE_KEY: DeviceKey,
},
}),
),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("respondToAuthChallengeRes3b:");
console.log(respondToAuthChallengeRes3b);
// ---------- Signin 3. respond to DEVICE_PASSWORD_VERIFIER challenge ----------
// `NotAuthorizedException: Incorrect username or password.`
// Must be doing something wrong around here . . .
const signedSrpSessionWithDevice3 = signSrpSessionWithDevice(
signedSrpSession3,
respondToAuthChallengeRes3b,
DeviceGroupKey,
DeviceSecretVerifierConfig.RandomPassword,
);
const respondToAuthChallengeRes3c = await cognitoIdentityProviderClient
.send(
new RespondToAuthChallengeCommand(
wrapAuthChallenge(signedSrpSessionWithDevice3, {
ClientId: clientId,
ChallengeName: "DEVICE_PASSWORD_VERIFIER",
ChallengeResponses: {
SECRET_HASH: secretHash,
USERNAME: username,
DEVICE_KEY: respondToAuthChallengeRes3b.ChallengeParameters.DEVICE_KEY,
},
}),
),
)
.catch((err) => {
console.error(err);
throw err;
});
console.log("respondToAuthChallengeRes3c:");
console.log(respondToAuthChallengeRes3c);
})();
Hey @simonmcallister0210 thanks for your update! I will start checking it today!
Hey @renatoargh Were you able to figure out why you were getting // NotAuthorizedException: Incorrect username or password.
?
I am facing the same issue.
Hey @spatel2693 we had a big detour on the project and I will be back to it next Monday. So far no progress but if I figure something out I will post it here
Hey @renatoargh, @simonmcallister0210 do you have any updates on this? I'm stuck on this error.
Hey. I had a look at this a month ago and couldn't make any progress.. but I think it's just a case of getting the algorithm correct. I must be making a mistake in the calculation somewhere. I'll have another go over the next few days
Hey. I had a look at this a month ago and couldn't make any progress.. but I think it's just a case of getting the algorithm correct. I must be making a mistake in the calculation somewhere. I'll have another go over the next few days
Thank you, @simonmcallister0210. I really appreciate this. If you enable sponsoring on this repo, I will make sure to send you a few π» !
Have a working demo on the linked branch. Just need to polish it off, write tests, update docs, etc.
Hey! π
I am currently working with Cognito's remember devices feature so I would like to know whether it would be possible to add support for the
DEVICE_SRP_AUTH
andDEVICE_PASSWORD_VERIFIER
challenges which are necessary to authenticate remembered devices.My current (working) code is this;
The code above works fine! The problem is that when I have a
deviceKey
on local storage I then get two more challenges after this point, that areDEVICE_SRP_AUTH
andDEVICE_PASSWORD_VERIFIER
. I have found instructions here: https://repost.aws/knowledge-center/cognito-user-pool-remembered-devices#:~:text=Call%20RespondToAuthChallenge%20for%20DEVICE_SRP_AUTH but all seems super cryptic to me and I was unable to adaptcognito-srp-helper
to work with the device challenges.Would it be possible to extend this lib to add support for this feature or maybe tell me what steps to follow in order to make it work with the current state of the library?
More references here: https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-device-tracking.html#user-pools-remembered-devices-signing-in-with-a-device
thank you a lot, great work guys!!!!