01 Apr 2019, 00:00

Secure and Easy 2FA in Nextcloud

At Nextcloud we are focused on security and usability. Often these two things conflict. In the last few months we have been working hard to make sure that two-factor authentication is easy to setup and easy to use for all users!

Without much further delay. I’m proud to introduce the next step in two-factor authentication the It is really me - Provider.

Note: the app is still under heavy development. Still we appreciate testing and feedback!

This provider as a second factor just presents the user with a button with the explicit instructions when to press it. Now a legitimate user can safely press the button and an evil hacker will most likely follow the clear instuctions and try to find another attack point.

Writing a two-factor provider in Nextcloud is extremely simple. And I urge all developers to have a look at the code to see how easy it is.

The heart of the app is the Provider Class that implements OCP\Authentication\TwoFactorAuth\IProvider. This intreface guides you with just a few methods to contructs your provider. On top of that we also chose to in this case implement the OCP\Authentication\TwoFactorAuth\IProvidesPersonalSettings.

If we have a quick look at the actual functions you can see there is not much magic:

	/**
	 * Get unique identifier of this 2FA provider
	 *
	 * @return string
	 */
	public function getId(): string {
		return Application::APP_ID;
	}

	/**
	 * Get the display name for selecting the 2FA provider
	 *
	 * @return string
	 */
	public function getDisplayName(): string {
		return $this->l10n->t('It is really me');
	}

	/**
	 * Get the description for selecting the 2FA provider
	 *
	 * @return string
	 */
	public function getDescription(): string {
		return $this->l10n->t('Easy and secure validation of user');
	}

	/**
	 * Get the template for rending the 2FA provider view
	 *
	 * @param IUser $user
	 * @return Template
	 */
	public function getTemplate(IUser $user): Template {
		$tmpl = new Template(Application::APP_ID, 'challenge');
		$tmpl->assign('user', $user->getDisplayName());
		return $tmpl;
	}

	/**
	 * Verify the challenge provided by the user
         *
         * @param IUser $user
         * @param string $challenge
         * @return bool
         */
	public function verifyChallenge(IUser $user, string $challenge): bool {
		return true;
	}

	/**
         * Check if this provider is enabled for the current user
         *
         * @param IUser $user
         * @return bool
         */
	public function isTwoFactorAuthEnabledForUser(IUser $user): bool {
		return $this->config->getAppValue(Application::APP_ID, $user->getUID() . '_enabled', '0') === '1';
	}

	/**
         * Get the Personalsettings
         *
         * @param IUser $user
         * @return IPersonalProviderSettings
         */
	public function getPersonalSettings(IUser $user): IPersonalProviderSettings {
		return new Personal(
			$this->config->getAppValue(Application::APP_ID, $user->getUID() . '_enabled', '0') === '1',
			$this->initialStateService
		);
	}

All the other logic in the app is just more helper functions. Some php classes to allow enabling the providers. Some javascript to make the frontend work nicely etc.

Of course now you want to see how it looks:

Testing is much appreciated, as well as feedback, suggestions or pull requests. Please all leave them at github.