chillerlan/php-authenticator
PHP 8.4+ library to generate and verify HOTP (RFC 4226) and TOTP (RFC 6238) one-time passwords, compatible with Google Authenticator-style apps. Includes optional Steam Guard time sync plus constant-time encoding helpers for safer key handling.
Install the package:
composer require chillerlan/php-authenticator
Basic TOTP (Time-Based) Usage:
use chillerlan\Authenticator\Authenticator;
$auth = new Authenticator();
$secret = $auth->createSecret(); // Generate a secret (e.g., "JBSWY3DPEHPK3PXP")
$auth->setSecret($secret); // Store the secret for future use
// Generate a QR code URI for Google Authenticator
$uri = $auth->getUri('MyApp', 'MyCompany');
// Output: otpauth://totp/MyApp?secret=JBSWY3DPEHPK3PXP&issuer=MyCompany
Verify a Code:
$userInput = '123456'; // User's input
if ($auth->verify($userInput)) {
// Valid OTP
}
endroid/qr-code) and the secret as a fallback.users.otp_secret).Setup:
$auth = new Authenticator([
'digits' => 6,
'period' => 30,
'algorithm' => AuthenticatorInterface::ALGO_SHA256,
]);
User Registration:
$secret = $auth->createSecret();
$user->otp_secret = $secret;
$user->save();
// Generate QR code URI
$uri = $auth->getUri('user@example.com', 'MyApp');
Login Flow:
$userInput = request('otp_code');
$auth->setSecret($user->otp_secret);
if ($auth->verify($userInput)) {
// Authenticate user
}
Setup:
$auth = new Authenticator([
'mode' => AuthenticatorInterface::HOTP,
]);
User Registration:
$secret = $auth->createSecret();
$user->otp_secret = $secret;
$user->otp_counter = 0; // Initialize counter
$user->save();
Login Flow:
$userInput = request('otp_code');
$auth->setSecret($user->otp_secret);
if ($auth->verify($userInput, $user->otp_counter)) {
$user->otp_counter++; // Increment counter
$user->save();
// Authenticate user
}
Service Provider:
use chillerlan\Authenticator\Authenticator;
class AuthServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(Authenticator::class, function () {
return new Authenticator(config('auth.otp'));
});
}
}
Config (config/auth.php):
'otp' => [
'digits' => 6,
'period' => 30,
'algorithm' => \chillerlan\Authenticator\AuthenticatorInterface::ALGO_SHA256,
'mode' => \chillerlan\Authenticator\AuthenticatorInterface::TOTP,
],
Middleware for OTP Verification:
use chillerlan\Authenticator\Authenticator;
class VerifyOtpMiddleware
{
public function handle($request, Closure $next)
{
$auth = app(Authenticator::class);
$auth->setSecret(auth()->user()->otp_secret);
if (!$auth->verify($request->otp_code)) {
return redirect()->back()->withErrors(['otp' => 'Invalid code']);
}
return $next($request);
}
}
Custom Secret Storage:
Use AuthenticatorInterface::setRawSecret() to store the raw binary secret in the database (e.g., users.otp_secret_raw).
$auth->setRawSecret($rawSecret); // Store raw binary secret
$auth->setSecret($encodedSecret); // Use encoded secret for verification
Dynamic Options: Override options per user:
$auth = new Authenticator([
'digits' => $user->otp_digits ?? 6,
'period' => $user->otp_period ?? 30,
]);
Secret Storage:
setRawSecret() for raw binary storage if needed.Time Synchronization:
useLocalTime: false and forceTimeRefresh: true for Steam Guard compatibility:
$auth = new Authenticator([
'useLocalTime' => false,
'forceTimeRefresh' => true,
]);
Adjacent Codes:
adjacent: 1 allows verification of the current and previous code.adjacent: 2 for 1-minute window with 30-second period).HOTP Counter:
users.otp_counter).URI Compatibility:
algorithm).PHP Extensions:
ext-sodium for constant-time encoding (fallback to paragonie/constant_time_encoding if missing).ext-curl is needed for Steam Guard server time sync.Verify Secrets:
Authenticator::code():
$auth->setSecret($user->otp_secret);
$expectedCode = $auth->code(); // Current TOTP code
Time Offsets:
$auth->setOptions(['time_offset' => 30]); // Adjust for timezone
HOTP Counter:
$auth->verify($otp, $counter);
logger("HOTP Counter: $counter");
Algorithm Mismatch:
algorithm in the URI matches the one used in Authenticator:
$uri = $auth->getUri('label', 'issuer', null, false); // Include algorithm in URI
Custom Encoders:
EncoderInterface for custom encoding/decoding (e.g., for legacy systems).Event Hooks:
Authenticator to trigger events (e.g., before/after verification):
class CustomAuthenticator extends Authenticator
{
public function verify($otp, $data = null)
{
event(new VerifyingOtp($otp, $data));
$result = parent::verify($otp, $data);
event(new OtpVerified($result));
return $result;
}
}
Database Models:
use chillerlan\Authenticator\Authenticator;
class User extends Model
{
public function verifyOtp($code)
{
$auth = app(Authenticator::class);
$auth->setSecret($this->otp_secret);
return $auth->verify($code);
}
}
Fallback Codes:
// Example: Generate and store backup codes
$backupCodes = str_split(str_random(16));
$user->backup_codes = json_encode($backupCodes);
$user->save();
How can I help you explore Laravel packages today?