afrikpay/otp-bundle
Symfony bundle for generating and validating one-time passwords (OTP). Install via Composer, register the bundle, run migrations, and use console commands to manage OTP workflows (create/enable/disable/run) for verification flows and scheduled processing.
Installation:
composer require afrikpay/otp-bundle
Add to config/bundles.php (Symfony 4.3+):
return [
// ...
Afrikpay\OTPBundle\AfrikPayOTPBundle::class => ['all' => true],
];
Database Migration: Run migrations to create the OTP-related tables:
php bin/console make:migration
php bin/console doctrine:migrations:migrate
First Use Case: Generate and send an OTP via a controller:
use Afrikpay\OTPBundle\Service\OTPService;
class AuthController extends AbstractController
{
public function sendOtp(OTPService $otpService)
{
$phone = '+2348123456789';
$otp = $otpService->generateAndSend($phone);
return new JsonResponse(['otp_id' => $otp->getId()]);
}
}
Verify OTP:
public function verifyOtp(OTPService $otpService, Request $request)
{
$otpId = $request->request->get('otp_id');
$code = $request->request->get('code');
$result = $otpService->verify($otpId, $code);
return new JsonResponse($result);
}
OTP Generation & Sending:
// Generate and send OTP (auto-expiry: 5 mins by default)
$otp = $otpService->generateAndSend($phoneNumber, [
'template' => 'Your OTP is {code}. Valid for 5 mins.',
'expiry' => 300, // 5 mins in seconds
]);
Manual OTP Handling:
// Generate OTP without sending
$otp = $otpService->generate($phoneNumber);
// Send existing OTP
$otpService->send($otp);
Verification:
$result = $otpService->verify($otpId, $userInputCode);
// Returns: ['success' => true/false, 'message' => '...']
Resending OTP:
$otpService->resend($otpId); // Resends last OTP for this phone
Event Listeners:
Subscribe to OTP events (e.g., otp.sent, otp.verified) in config/services.yaml:
services:
App\EventListener\OTPListener:
tags:
- { name: kernel.event_listener, event: otp.sent, method: onOtpSent }
Custom Templates:
Override default SMS templates in config/packages/afrikpay_otp.yaml:
afrikpay_otp:
templates:
default: "Your verification code is {code}. Do not share it."
Rate Limiting: Use Symfony’s rate limiter to restrict OTP requests:
use Symfony\Component\RateLimiter\RateLimiterFactory;
$factory = new RateLimiterFactory();
$limiter = $factory->create($request->getClientIp());
if (!$limiter->consume(5, 60)) {
throw new \RuntimeException('Too many requests');
}
Testing:
Mock the OTPService in tests:
$otpService = $this->createMock(OTPService::class);
$otpService->method('verify')->willReturn(['success' => true]);
Cron Dependency:
cron:run) for background tasks (e.g., OTP expiry cleanup).$otpService->cleanupExpired(); // Call this periodically
Database Schema:
otp table already exists.src/Resources/migrations/ for the schema and adjust manually if needed.Phone Number Format:
+2348123456789).$phone = preg_replace('/[^0-9+]/', '', $rawPhone);
SMS Provider Integration:
Afrikpay\OTPBundle\Service\SmsProviderInterface:
class AfrikpaySmsProvider implements SmsProviderInterface
{
public function send(string $phone, string $message): bool
{
// Integrate with Afrikpay API
}
}
Register it in services.yaml:
afrikpay_otp.sms_provider: '@App\Service\AfrikpaySmsProvider'
Enable Logging:
Add to config/packages/monolog.yaml:
handlers:
otp:
type: stream
path: "%kernel.logs_dir%/otp.log"
level: debug
channels: ["afrikpay_otp"]
OTP Expiry:
afrikpay_otp:
default_expiry: 600 # 10 minutes
Verification Errors:
otp_id in DB).otp.sent event).Testing Locally:
$otpService->setSmsProvider(new MockSmsProvider());
Custom OTP Storage:
Extend Afrikpay\OTPBundle\Entity\OTP or use a custom repository:
class CustomOTPRepository extends ServiceEntityRepository
{
public function findByPhone(string $phone): ?OTP
{
// Custom logic
}
}
Multi-Channel OTP:
Support email/email OTP by implementing MultiChannelOTPService:
class MultiChannelOTPService extends OTPService
{
public function sendViaEmail(string $email, string $code): bool
{
// Logic for email OTP
}
}
Webhook Validation: Validate OTPs via webhooks (e.g., Afrikpay callback):
use Afrikpay\OTPBundle\Event\OTPWebhookEvent;
public function onOtpWebhook(OTPWebhookEvent $event)
{
if ($event->isValid()) {
$otpService->markAsVerified($event->getOtpId());
}
}
How can I help you explore Laravel packages today?