-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature request: add support to DEVICE_SRP_AUTH
and DEVICE_PASSWORD_VERIFIER
challanges
#34
Comments
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 |
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 ** // 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 |
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. |
Thank you so much, @simonmcallister0210 , for a quick turnaround. Really appreciate it. I'll give it a try and let you know. |
Made a rod for my own back writing so many unit tests, not sure how useful most of those tests are tbh... just need to finish the integration test cases and documentation then we should be good to go |
Hello again! I've returned to the project where this feature would be extremely helpful. I attempted to test the code in this branch, but unfortunately, I ran into some issues. Would it be possible for you to publish the contents of this branch as a beta tagged release on npm? Thanks so much for your help! It looks like the device support is in place, and I suspect the issues might be on my end. |
Sure. I've published 2.3.0-beta-v1. If all goes well on the beta release I'll merge this branch in next week / week after. Let me know if there's any issues 👍 One thing to be aware of: the request wrapper functions ( const USER_ID_FOR_SRP = initiateAuthRes2.ChallengeParameters?.USER_ID_FOR_SRP;
// . . .
new RespondToAuthChallengeCommand(
wrapAuthChallenge(signedSrpSession, {
ClientId: clientId,
ChallengeName: "SOFTWARE_TOKEN_MFA",
ChallengeResponses: {
SECRET_HASH: secretHash,
SOFTWARE_TOKEN_MFA_CODE: otp,
USERNAME: USER_ID_FOR_SRP, // <-- here
},
Session: session,
}),
, The integration tests are a good reference if you need it. In a future release I'll just attach |
Hey, thanks for your quick response! I am testing it now (successfully installed the beta version from npm) and I am now adapting my code because currently, I am still using the workaround from #26 A quick question; what exactly is the secretId, as mentioned here (in my case I am not generating client secrets): |
When you enable secrets you will get that secret ID for the app. Then you'd need to call that function to calculate the value for SECRET_HASH, which you need to pass to all your Cognito API requests. Since you dont have a secret you won't need to provide a SECRET_HASH |
Hey @simonmcallister0210, thank you so much. I can confirm that I am finally able to make the entire flow work! 🥳 |
It's ok, I'm just happy people are using the library 🙂 thanks for your patience I'll wait for another week in case there are any issues, then I'll release the beta fix in 2.3.0 |
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!!!!
The text was updated successfully, but these errors were encountered: