To verify the OTP you received, you have to create an OTP object with the mandatory parameters that are namely:
- The secret,
- The digest,
- The digits,
- The period (TOTP only) or the current counter (HOTP only).
In general, the digest and digits are the same for all account and might not change. Same goes for the period. The secret, the current counter and the optional label depends on the user account.
<?php
use OTPHP\TOTP;
$digits = 6;
$digest = 'sha1';
$period = 30;
$totp = TOTP::createFromSecret($user->getOtpSecret());
$totp->setPeriod($period);
$totp->setDigest($digest);
$totp->setDigits($digits);
$totp->setLabel($user->getEmail());
$totp->verify($_POST['otp']);
If you use TOTP, storing the secret should be enough. However if you store only that information, you will have troubles if your application security policy evolves:
- You decide to increase the number of digits from
6
to8
. - You decide to change the hashing algorithm from
'sha1'
to'sha256'
. - You decide to change the period from
30
to20
. - You need to use custom parameters you set during the application configuration.
None of your end-user will be able to use the OTP generated by their application.
Another approach could be to store the entire provisioning Uri. As the provisioning Uri contains all parameters including the custom ones, then only the new provisioning will be affected by your new security policy. Old provisioning Uris may be updated step by step (e.g. when the end-user is logged in).
<php
use OTPHP\Factory;
$otp = Factory::loadFromProvisioningUri(
$user->getProvisioningUri()
);
$otp->verify($_POST['otp']);
Note: if your OTP is an HOTP, then you have to store the provisioning Uri again as the current counter changed.
A TOTP ALWAYS lives for the period you defined.
If you try the following code lines, you may see 2 different OTPs.
<?php
use OTPHP\TOTP;
$totp = TOTP::generate(10); // TOTP with an 10 seconds period
for ($i = 0; $i < 10; $i++) {
echo 'Current OTP is: '. $totp->now();
sleep(1);
}
The reason is that the OTP lifetime does not start on your first request ($totp->now()
)
but may have started few seconds ago.