composer require spomky-labs/otphp
use OTPHP\TOTP;
$secret = TOTP::generate(); // Generates a random Base32 secret
$totp = TOTP::createFromSecret($secret);
$code = $totp->now(); // Current OTP code
use OTPHP\HOTP;
$secret = HOTP::generate();
$hotp = HOTP::createFromSecret($secret);
$code = $hotp->at(1); // OTP code for counter=1
// Store this in your user model/database
$userSecret = 'JBSWY3DPEHPK3PXP'; // Base32 encoded
// During login
$totp = TOTP::createFromSecret($userSecret);
$isValid = $totp->verify($_POST['otp_code']);
// Generate provisioning URI for QR code
$totp = TOTP::createFromSecret($userSecret)
->withLabel('user@example.com')
->withIssuer('MyApp');
$provisioningUri = $totp->getProvisioningUri();
$qrCodeUrl = $totp->getQrCodeUri(); // Requires GD library
public function verifyOtp(Request $request)
{
$user = User::find($request->user_id);
$otp = Factory::loadFromProvisioningUri($user->provisioning_uri);
return $otp->verify($request->otp_code)
? response()->json(['success' => true])
: response()->json(['error' => 'Invalid OTP'], 401);
}
// Store counter in database
$user->hotp_counter = $hotp->getCounter();
$user->save();
// Next verification
$hotp = HOTP::createFromSecret($user->secret)
->withCounter($user->hotp_counter + 1);
// app/Providers/OtpServiceProvider.php
public function register()
{
$this->app->singleton(OTPFactory::class, function() {
return new Factory();
});
}
// config/otp.php
return [
'default' => [
'digits' => 6,
'algorithm' => 'sha256',
'period' => 30,
],
'admin' => [
'digits' => 8,
'algorithm' => 'sha512',
'period' => 60,
]
];
// Usage
$totp = TOTP::createFromSecret($secret)
->withConfig(config('otp.admin'));
// For existing users upgrading security settings
public function upgradeOtp(User $user)
{
$newSecret = TOTP::generate();
$newOtp = TOTP::createFromSecret($newSecret)
->withLabel($user->email)
->withIssuer(config('app.name'))
->withDigits(config('otp.default.digits'))
->withAlgorithm(config('otp.default.algorithm'));
$user->update([
'otp_secret' => $newSecret,
'provisioning_uri' => $newOtp->getProvisioningUri()
]);
}
Secret Storage:
Time Synchronization:
// For TOTP, ensure server time matches user devices
$totp->verify($code, time() - 30, time() + 30); // 30s leeway
HOTP Counter Management:
Algorithm Selection:
$totp->withAlgorithm('sha256'); // Recommended minimum
Verification Debugging:
try {
$isValid = $totp->verify($code);
} catch (\OTPHP\Exception\InvalidParameterException $e) {
// Handle parameter errors
} catch (\OTPHP\Exception\SecretDecodingException $e) {
// Handle secret decoding issues
}
Time Window Issues:
// Check current time window
$currentTime = time();
$windowStart = $currentTime - ($totp->getPeriod() * $totp->getWindow());
$windowEnd = $currentTime + ($totp->getPeriod() * $totp->getWindow());
// Log for debugging
\Log::debug("Verification window: {$windowStart}-{$windowEnd}");
QR Code Generation:
php-gd)try {
$qrCode = $totp->getQrCode();
} catch (\RuntimeException $e) {
// Fallback to provisioning URI
return response()->json(['uri' => $totp->getProvisioningUri()]);
}
Default Values:
$totp->withDigits(6)
->withAlgorithm('sha256')
->withPeriod(30);
Label Formatting:
:) in labelsissuer:account$totp->withIssuer('MyApp')
->withLabel('user@example.com');
Provisioning URI Parsing:
try {
$otp = Factory::loadFromProvisioningUri($uri);
} catch (\OTPHP\Exception\InvalidProvisioningUriException $e) {
// Handle invalid URI
}
Custom Verification Logic:
$customVerifier = new class($totp) implements OTPVerifierInterface {
public function verify($code, $timestamp = null, $window = null)
{
// Custom verification logic
return $this->otp->verify($code, $timestamp, $window) &&
$this->additionalCheck($code);
}
};
Event Dispatching:
// Listen for OTP generation
event(new OTPGenerated($totp, $code));
// In your event handler
public function handle(OTPGenerated $event)
{
// Log or notify about new OTP generation
}
Caching Strategy:
// Cache OTP objects for performance
$cacheKey = "otp:{$user->id}";
$otp = Cache::remember($cacheKey, 60, function() use ($user) {
return Factory::loadFromProvisioningUri($user->provisioning_uri);
});
Multi-Factor Integration:
// Combine with Laravel's auth
public function authenticate(Request $request)
{
$user = User::find($request->user_id);
$otp = Factory::loadFromProvisioningUri($user->provisioning_uri);
if (!$otp->verify($request->otp_code)) {
return back()->withErrors(['otp' => 'Invalid code']);
}
Auth::login($user);
}
How can I help you explore Laravel packages today?