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

TOTP implementation for 2FA #1031

Merged
merged 16 commits into from Oct 25, 2019
Merged

TOTP implementation for 2FA #1031

merged 16 commits into from Oct 25, 2019

Conversation

colinodell
Copy link
Contributor

Implements #1020 using TOTP codes

Overview

A new section has been added to the user area:

image

Clicking the button link takes you to this new page:

image

Using a compatible app, the user must scan the QR code and enter in a valid TOTP code to prove they've got it working. Once they submit a valid code 2FA will be active on their account:

image

The user is also given a single-use backup code - more on that later.

When a user with a 2FA-enabled account logs in, we redirect them to this new page:

image

They must enter a valid TOTP code from their authenticator in order to continue and be fully logged-in. They can also request that the current browser be remembered for 30 days so they aren't prompted to constantly re-enter TOTP codes from the current computer.

If the user loses access to their authenticator, they can instead use that backup code to gain access. Using this code will cause 2FA to become disabled until they set it up again:

image

Users can also disable 2FA manually if they wish. From the main 2FA status in their account area, they simply click that giant red button which takes them to a confirmation page:

image

Clicking the red button will disable 2FA:

image

Notes

A few notes about this implementation:

  • This 2FA functionality works with OAuth accounts too!
  • I've added very basic rate-limiting with Redis - you basically get locked out for 15 minutes after entering 5 incorrect codes
  • Users are sent an email notification whenever 2FA is enabled or disabled
  • I added new mobile icon to represent 2FA in the menu
  • The bundle I'm using does offer the ability to use email-based codes, but I have not implemented that

I'll add some additional notes inline with the code

Happy #Hacktoberfest!

enabled: true
server_name: '%packagist_host%'
issuer: Packagist
window: 1
Copy link
Contributor Author

Choose a reason for hiding this comment

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

A new code is generated every 30 seconds. A window of 1 will allow three codes to be accepted:

  • The current code
  • The code generated 30 seconds before it
  • The next code that will be generated

This allows some flexibility in case the server clock and mobile clock are slightly out-of-sync, or the user needs a few extra seconds to enter in their code.

throw new AccessDeniedException('You cannot change this user\'s two-factor authentication settings');
}

if ($backupCode = $this->get('session')->get('backup_code')) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I briefly considered using a flash message for this, but I wanted special formatting and therefore opted to just stash the code in normal session storage and manually remove it once we read it.

return $this->backupCode !== null && hash_equals($this->backupCode, $code);
}

public function invalidateBackupCode(string $code): void
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The interface assumes the user may have multiple codes, but we're only using a single code here.

@@ -0,0 +1,178 @@
{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to manually look up each icon that was used to create the custom font, which took a while, so I figured why not commit this so it's easier to make future changes?

@colinodell
Copy link
Contributor Author

Thanks for the review, @stof! I have made the suggested edits. For the sake of tracking them, I have not squashed these into my earlier commits yet but can certainly do so to clean up the history before merging.

@colinodell colinodell mentioned this pull request Oct 9, 2019
Copy link

@afk11 afk11 left a comment

Choose a reason for hiding this comment

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

concept & code review ACK - PR looks great, and the two-factor-bundle & otphp deps are tested and actively maintained

Copy link
Contributor

@stof stof left a comment

Choose a reason for hiding this comment

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

Looks good to me

@colinodell
Copy link
Contributor Author

I have added commit 00ca834 which exposes the secret key to the user during setup. This will allow people without QR code scanners to set up 2FA:

image

@Seldaek
Copy link
Member

Seldaek commented Oct 10, 2019

Just a quick flow review based on the screenshots (thanks for that, really helpful), it looks great overall, I'd just think having a proper intermediate page to show the confirmation that it's been enabled and show the backup code there prominently would be good. Having it only in the flash message confirmation can be very easily glanced over IMO.

@colinodell
Copy link
Contributor Author

Great idea! I'll work on implementing that over the next few days.

Implements #1031 (comment)

Also includes a new checkbox icon used for the confirmation button
Additional testing revealed an issue with Github-based accounts.  When
such an account was logged in for a while, they eventually lost their
'fully-authenticated' status and would be forced to re-connect with
Github, which was an unexpected behavior. Furthermore, even if you went
through that process, you wouldn't become fully-authenticated
afterwards.

Instead of spending time to investigate this behavior, I've decided to
revert to the convention used by the 'change password' functionality,
which doesn't require this additional permission.
@colinodell
Copy link
Contributor Author

I found time this evening to implement it 😃

When the user is setting up 2FA and they submit the confirmation code from their authenticator, they will be taken to this new intermediate confirmation screen:

image

2FA is fully enabled at this point (as per the green success message). The green button is simply a link to the main page showing 2FA status, which again confirms that 2FA is enabled:

image

(You don't actually have to click the big green button for 2FA to become enabled)

Hopefully this makes it more obvious that the user should save their backup code in a safe place.

@colinodell
Copy link
Contributor Author

I don't like how that giant "disable two-factor auth" button detracts from the green success message, so I've turned that into a link instead:

image

(That link still takes the user to the confirmation page which does still have a giant red button on it.)

@Seldaek Seldaek merged commit 19eb8c1 into composer:master Oct 25, 2019
@Seldaek
Copy link
Member

Seldaek commented Oct 25, 2019

Thanks, looking all good! It's live.

@Slamdunk
Copy link

Awesome 👍

Minor GUI flaw: in the login the checkbox for "Trust this computer" is unaligned and overlaps the input field

immagine

Agent: Firefox 70 on Xubuntu 19.10

css

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.

None yet

5 participants