Skip to content
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

Confirm OTP Token #72

Merged
merged 28 commits into from
May 20, 2024
Merged

Confirm OTP Token #72

merged 28 commits into from
May 20, 2024

Conversation

strouptl
Copy link
Contributor

@strouptl strouptl commented May 17, 2024

Hi there,

I think it is standard now to confirm that the user has properly configured the OTP token in their devise before enabling it. Otherwise, if they navigate away from the page without actually scanning the QR code or saving the recovery tokens, then they will be locked out of their account.

I have scaffolded a solution for this that simply asks you to enter a OTP successfully before actually enabling Two Factor Authentication. The key changes are as follows:

  • Add an "Edit" action with a two step form for enabling MFA
  • Require the user to enter one OTP as confirmation in the second step before enabling MFA
  • Distinguish the "populate_otp!" and "enable_otp!" methods in the otp_authenticable module

Please let me know if you like this solution. Also, please let me know if you have any information on the current status of the test suite: currently hitting an error and a number of deprecation notices with Rails 7.1.3/Ruby 3.2.3.

NOTE: this PR includes #73 .

Thanks!

@strouptl
Copy link
Contributor Author

Here is a screenshot from AWS for reference:

Screen Shot 2024-05-18 at 12 10 39 AM

@strouptl
Copy link
Contributor Author

OK, I have resolved the main errors (#73), and updated the test suite for this change.

@strzibny
Copy link
Collaborator

strzibny commented May 18, 2024

Thanks for the PR. I merged the first one. Can you please rebase and make sure we have green test run?

@strouptl
Copy link
Contributor Author

strouptl commented May 18, 2024

OK, I have rebased. Waiting on the test run now.

…ow" view to display link to form if otp is disabled;
…ing/enabling Two-Factor-Authentication (rather than separate otp_confirm_tokens);
… was enabled, and to check for actual page content rather than flash message content for reliability;
@strouptl
Copy link
Contributor Author

OK, the CI checks are passing now! Let me know if you have any additional requests/comments.

@strzibny
Copy link
Collaborator

strzibny commented May 19, 2024

Hi, I tried adding this change to an existing project by adding the new "edit" view. But importing the QR code stopped working and I don't see the manual provisioning code. Is there something more that you expect for upgrading existing apps?

Screenshot 2024-05-19 at 10 44 16

@strouptl
Copy link
Contributor Author

strouptl commented May 19, 2024

Ah, sorry about that. I had added a "populate_otp!" method so that existing apps can populate the OTP auth and recovery secrets for each user (since existing users have skipped the create callback), but removed it to try to minimize the delta for this PR. It sounds like it may be necessary after all, though.

I will reapply the change and update the README with instructions for existing apps so that you can test it out.

@strouptl
Copy link
Contributor Author

When the secret is populated the screen works, however then /otp/token path takes you to the enabled OTP settings. So something is not working right.

Interesting. otp_tokens#show should only show the token and secret if otp_enabled? returns true for that user.

Is OTP enabled in the database for this user (user.otp_enabled?)? Maybe somehow the otp_enabled boolean got set to "true" in the master branch?

@strzibny
Copy link
Collaborator

I'll double check

@strzibny
Copy link
Collaborator

@strouptl seems I faced the issue because it does enable OTP but it's not possible to disable it. When I go for disabling it sends me to an edit it seems but in database the otp_enabled is still true. Can you check?

@strouptl
Copy link
Contributor Author

strouptl commented May 19, 2024

@strouptl seems I faced the issue because it does enable OTP but it's not possible to disable it. When I go for disabling it sends me to an edit it seems but in database the otp_enabled is still true. Can you check?

Hi @strzibny, I just double checked, and it is working for me, so I am still thinking you must have enabled OTP somehow in the master branch.

Perhaps it is indeed better to populate the OTP secrets as needed in the otp_tokens#edit action, though, as it would resolve potential confusion like this altogether.

Here are the steps I followed, if you still want to verify them for the current issue you are seeing:

First round (without populated secrets)

  1. Create a user in an existing application with no OTP
  2. Add OTP to application and user model
  3. Login as user and visit /otp/token path (shows "Disabled")
  4. Click "Enable..." (i.e. visit /otp/token/edit) and view form (shows blank provisioning code, as you saw)
  5. Try submitting form with blank and false Confirmation Codes (displays "did not match" error)

Second round (with populated secrets)

  1. Call "populate_otp!" on user
  2. Visit /otp/token/edit again (shows populated provisioning code)
  3. Visit /otp/token path again (still shows "Disabled")

@strouptl
Copy link
Contributor Author

strouptl commented May 19, 2024

@strouptl seems I faced the issue because it does enable OTP but it's not possible to disable it. When I go for disabling it sends me to an edit it seems but in database the otp_enabled is still true. Can you check?

@strzibny, or are you wondering about how to disable OTP with the new functionality?

In master, you do indeed use a form for both enabling and disabling OTP. However, with the new functionality, the edit action and the form it displays are only for enabling OTP.

To disable OTP, you must click the "Reset your Two Factors Authentication status" button, which points to the "destroy" action. This follows the same workflow as AWS, where disabling 2FA == removing your secret, as well.

#
# Updates the status of OTP authentication
#
def update
enabled = params[resource_name][:otp_enabled] == "1"
if enabled ? resource.enable_otp! : resource.disable_otp!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is the broken behaviour I am seeing. Before your change you could temporarily disable OTP and I think we should preserve the functionality for the time being.

@strzibny
Copy link
Collaborator

I confirm that completely removing it works and commented inline where I think it's broken for the simple 'disabling of OTP'. Any reason why it shouldn't stay there?

@strouptl
Copy link
Contributor Author

I confirm that completely removing it works and commented inline where I think it's broken for the simple 'disabling of OTP'. Any reason why it shouldn't stay there?

I think the argument would be that there is no way to distinguish between temporarily and permantly disabling MFA. For example, a person could click "disable 2FA" intending to reenable it shortly, but then change authenticator devices, and then come back a month later to reenable OTP not realizing that they lost the secret.

So we need to require a confirmation code anytime we enable OTP. And if this is the case, we might as well reset the secrets so that there is no confusion over whether you need to scan the QR code or not at the "Enable 2FA" form.

Maybe the "Reset functionality" is the one that needs to change. Could we use the current button and destroy action for disabling OTP, and add a new "reset" action which resets your secrets and disables OTP, but then redirects you to the enable OTP form so that you can obtain your new QR Code/secret?

@strouptl
Copy link
Contributor Author

I confirm that completely removing it works and commented inline where I think it's broken for the simple 'disabling of OTP'. Any reason why it shouldn't stay there?

We could do a fuller survey to see how others have handled this, but it looks to me like both AWS and Authy do not allow temporarily disabling MFA (i.e. they will always reset the secret).

@strzibny
Copy link
Collaborator

Yes that's fair. We'll just document the change and cancel it then.

@strouptl
Copy link
Contributor Author

I confirm that completely removing it works and commented inline where I think it's broken for the simple 'disabling of OTP'. Any reason why it shouldn't stay there?

We could do a fuller survey to see how others have handled this, but it looks to me like both AWS and Authy do not allow temporarily disabling MFA (i.e. they will always reset the secret).

UPDATED: I double checked Google's 2FA functionality: Google does allow you to disable 2FA in general, but this is safer for them, as they allow multiple 2FA options (phone number, in-app Prompt, etc.), so you are less susceptible to getting locked out if you lose the OTP secret. And they include warnings with this functionality, as well:

Screen Shot 2024-05-20 at 6 28 13 AM

So I think we should consider this functionality as out-of-scope for devise-otp: you can implement it if desired, but there are risks that come with this, so you would want to warn your users, and would probably want to make sure you had several different MFA options available (not just OTP).

@strouptl
Copy link
Contributor Author

Yes that's fair. We'll just document the change and cancel it then.

Sorry for the confusion. Are you still good with this solution?

@strzibny
Copy link
Collaborator

Yes, I am on board. But let's populate the secret on edit so nobody has to go running some code in the terminal (especially for production)

@strouptl
Copy link
Contributor Author

strouptl commented May 19, 2024 via email

…e method to "populate_otp_secrets!", replace reset_otp_credentials! method with destroy_otp_secrets!, and remove "on create" callbacks for consistency;
…s for existing users from README (no longer needed);
…ce it includes counters and challenges as well);
@strouptl
Copy link
Contributor Author

strouptl commented May 19, 2024

Hi @strzibny, I have updated the PR so that the system operates on the assumption that nobody has OTP credentials unless they need them (i.e. by clicking "Enable...").

See details below, and let me know if you have any thoughts!

  1. Update otp_tokens#edit action to populate otp tokens as needed
  2. Replace "reset_otp_credentials!" method with "clear_otp_fields!" (blanks out all OTP values)
  3. Remove "on create" callback for generating OTP credentials
  4. Updated tests for changes

@strzibny
Copy link
Collaborator

Looking good!

As a last thing can you write an entry for https://github.com/wmlele/devise-otp/blob/master/CHANGELOG.md? I am missing last two versions but I'll update it retrospectively, you can put the new version as 'Unreleased'.

I think we should mention all removed I18n keys as well as new ones, the new edit action and view, replacement of reset_otp_credentials and removal of the callback.

strouptl and others added 3 commits May 20, 2024 16:56
…scope singular; fix spelling issue in "Enable Two Factor Authentication" link; expand "Changes to Locales" description in CHANGELOG;
@strouptl
Copy link
Contributor Author

Done.

@strzibny
Copy link
Collaborator

Thank you @strouptl

@strzibny strzibny merged commit 57e4c92 into wmlele:master May 20, 2024
1 check passed
@strouptl strouptl deleted the confirm_otp_token branch May 20, 2024 12:06
@strouptl
Copy link
Contributor Author

My pleasure!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants