rinvex/authy
Rinvex Authy brings Twilio Authy two-factor authentication to Laravel, with user registration, phone verification, token validation, and middleware-ready workflows for securing logins and sensitive actions. It offers configurable setup and a simple API for managing Authy users.
Install the package
composer require rinvex/authy
The service provider Rinvex\Authy\AuthyServiceProvider and facade Rinvex\Authy\Authy are auto-discovered on Laravel 5.5+.
Publish and wire up config
php artisan vendor:publish --tag=rinvex-authy:config
This creates config/authy.php. The only meaningful key is the Authy production API key:
AUTHY_KEY=your_api_key_here
Get an API key from https://dashboard.authy.com → Applications → API Keys. Use the production key; use the sandbox key while developing.
First use case — register a user and verify them:
use Rinvex\Authy\Authy;
$authyId = Authy::register($user->email, $user->phone, $user->country_code);
$user->update(['authy_id' => $authyId]);
// Later, on login:
$response = Authy::verify($user->authy_id, $request->input('token'));
Read vendor/rinvex/authy/src/ first — it's a thin wrapper over the authy/php SDK, so the real API surface lives in Authy.php plus AuthyServiceProvider.php. Everything else in this assessment is grounded in those two files.
Schema is yours. The package stores nothing in a database — you own the columns. The canonical additions to users are:
$table->string('authy_id')->nullable()->after('password');
$table->string('phone')->nullable();
$table->string('country_code', 4)->nullable();
Registration flow. Call Authy::register($email, $phone, $countryCode) after your user is persisted. The $sendSms argument (default true) fires an SMS token immediately. Wrap in a transaction-safe block: if Authy fails, roll back the user or mark them as 2FA-pending.
Login flow — two-step gate. Keep Authy::verify() out of your login controller and inside a middleware or a dedicated TwoFactorController. The pattern:
auth.2fa.pending = user_id)./2fa/challenge route (guarded by guest).Authy::verify($authyId, $token) → on success, Auth::loginUsingId() and session()->forget('auth.2fa.pending').This keeps the password and 2FA surfaces separate and avoids re-auth loops.
Middleware guard. For routes that require an already fully authenticated user, a simple EnsureTwoFactorPassed middleware that checks session('auth.2fa.passed') is cleaner than calling Authy on every request. Never call the Authy API per-request — it's rate limited and slow.
Fallback delivery.
Authy::requestSms($user->authy_id); // SMS
Authy::requestCall($user->authy_id); // Voice call
Expose both behind a single "Resend code" button with a small cooldown (e.g. 60s) so you don't burn Authy's rate limits.
OneTouch approval. Authy::registerActivity($authyId, $data) tells Authy what the login is for (IP, browser, geolocation) so the OneTouch push shows context. Pair this with a webhook route that Authy calls on approve/deny — this is where most teams wire up push-based login.
Soft-delete / GDPR. Call Authy::delete($authyId) on user deletion. This is not automatic; if you skip it, stale users remain billable in Authy's dashboard.
Testing. In feature tests, bind a fake facade:
Authy::shouldReceive('verify')->once()->andReturn(['success' => true]);
Never hit the network in CI.
$phone must not include the country code. Authy expects +1 (415) 555-0123 split as $phone = "4155550123", $countryCode = "1". Passing "+14155550123" as $phone will register a broken user that receives no SMS. Strip leading +, 0, and formatting before calling register().
The verify response is an array, not a boolean. A successful verify() returns something like ['success' => true, 'message' => 'Token is valid']. Compare $response['success'] === true, and check Authy::getUserStatus($authyId) if you need to distinguish "invalid token" from "user is banned / deleted".
Exceptions are your friend — until they're not. Authy:: methods throw on transport/API errors (invalid API key, network failure, rate limit). Distinguish wrong token (a success: false response) from API failure (an exception) in your UI: the former re-prompts, the latter should alert ops and log out.
Config key is key, not api_key. The package reads config('authy.key'). If you copy code from other Authy tutorials you'll get Authy::setApiKey(...) — that's the raw authy/php SDK, not this wrapper. Set AUTHY_KEY and trust the service provider.
Sandbox testing has fixed tokens. Authy's sandbox key returns tokens like 0000000 (7 zeros) for phone ending 007, 1111111 for 111, etc. Real production codes won't work in sandbox. Keep AUTHY_KEY in .env per-environment and add AUTHY_KEY to .env.example.
This package is thin and lightly maintained (35 stars). Before upgrading Laravel majors, verify the facade's getFacadeAccessor() still resolves under the new container. Because the wrapper is ~2 files, pinning a fork or migrating directly to authy/php is a realistic exit path if you hit a wall — the surface area is register, verify, requestSms, requestCall, delete, registerActivity, getUserStatus.
Country codes are strings, not ints. "44" for UK, "1" for US/Canada, "61" for AU. If you store as INT, +-less codes like "44" work but zero-padded codes (rare) will silently shift — store as VARCHAR(4).
Rate limits bite at scale. Authy throttles SMS per phone number per hour. Cache last-send time per user (Cache::put("authy:last_sms:{$id}", now(), 3600)) and refuse resends within the window rather than relying on the API error.
Debugging tips. Add a temporary Log::debug($response) inside the two call sites — the response includes message and Authy's own error codes (e.g. 60008 = invalid token). Authy's error codes are documented only on their site, so log them raw and map them in one place (config/authy.php is a fine place to add a messages map).
Extension point. If you need richer flows (device binding, custom OneTouch callbacks, retry policies), extend the facade target rather than editing the vendor package:
$this->app->bind(\Rinvex\Authy\Authy::class, \App\Services\MyAuthy::class);
in your AppServiceProvider::register().
How can I help you explore Laravel packages today?