✌️ Two-Factor Authentication

in Symfony

What is Multiple Factor Authentication?

Provide several separate pieces of evidence
to an authentication system

  • 💭 Knowledge
  • 🔑 Possession
  • 🖐 Inherence

Get money from an ATM

  • 🔑 Credit card
  • 💭 PIN

Classic web login

  • 💭 Username
  • 💭 Password
  • 🔑 Security Key

The Security Key

A physical device
generating a disposable code that is
near impossible to spoof.

The One-Time-Password

  • ✅ Can be verified
  • 👤 Includes key identity

One-Time-Password Authentication


Registration

  • 🔑 Assign a key to a user

Login

  • ❓ Ask for an OTP
  • ✅ Validate the OTP
  • ↔️ Ensure OTP match user key

Security in Symfony

Token

Hold the user's authentication information

AnonymousToken,
UsernamePasswordToken,
RememberMeToken

AuthenticationProvider

Create and authenticate Token

Are the username and password valid?
Does the given cookie exists is session?
...

FirewallListener

Capture the login requests,
call the authentication provider
and store the Token in the session.

✨ Where the magic operate ✨

Factory

Holds everything together and hook into the Security component

Define configuration for security.yml!

Username + Password + OTP
authentication system

UsernamePasswordOTPToken

Extends UsernamePasswordToken and adds a OneTimePassword attribute.

class UsernamePasswordOTPToken extends UsernamePasswordToken {
    /**
     * @var string
     */
    private $oneTimePassword;

    /**
     * {@inheritdoc}
     */
    public function __construct($user, $credentials, $oneTimePassword, $providerKey, array $roles = array())
    {
        parent::__construct($user, $credentials, $providerKey, $roles);

        $this->oneTimePassword = $oneTimePassword;
    }
}

Authenticate the token

SimpleForm, Guard or full custom provider

  • 👤 Load the user by username
  • 💭 Check if the provided password is valid
  • ✅ Validate the OTP via Yubico third-party services
  • ↔️ Ensure OTP key belongs to the user

Sample implementation


// Check that the user exists.
try {
    $user = $userProvider->loadUserByUsername($token->getUsername());
} catch (UsernameNotFoundException $e) {
    throw new BadCredentialsException('User not found.');
}

// Check that the provided password is valid.
if (!$this->encoder->isPasswordValid($user, $token->getCredentials())) {
    throw new BadCredentialsException('The presented password is invalid.');
}

$oneTimePassword = $token->getOneTimePassword();

// Check that the provided one-time-password is valid.
if (!$this->yubico->isValid($oneTimePassword)) {
    throw new BadCredentialsException('Invalid OTP.');
}

// Check that the provided one-time-password belongs to the user.
if ($this->getYubikey($user) !== $this->yubico->getIdentity($oneTimePassword)) {
    throw new BadCredentialsException('Yubico identities mismatch.');
}

// Everything's in order, move along.
return new UsernamePasswordOTPToken(
    $user,
    $user->getPassword(),
    $oneTimePassword,
    $providerKey,
    $user->getRoles()
);

Demo Time!

github.com/tom32i/symfony-yubikey-demo

Going further...

Two-Factor voter

Protect sensitive part of your apps with mandatory Two-Factor Auth

is_granted('IS_AUTHENTICATED_TWO_FACTOR')

UserSecurityKey Constraint

Ask for a OTP for a form to be valid
(just like UserPassword constraint)

Thanks for listening!

Questions and feedback?

@Tom32i