Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Authy Laravel Package

rinvex/laravel-authy

Rinvex Laravel Authy integrates Twilio Authy two-factor authentication into Laravel applications, letting you register users, verify TOTP and SMS tokens, and manage secure 2FA flows through simple configuration, a service provider, and a convenient facade.

Deep Wiki
Context7

Getting Started

Install and wire up

composer require rinvex/laravel-authy
php artisan vendor:publish --tag=rinvex-authy-config   # or --provider="Rinvex\Authy\Providers\AuthyServiceProvider"

Add the two credentials to .env — this package needs a production key and a sandbox key separately, and a flag to pick between them:

AUTHY_KEY=your_production_key
AUTHY_SANDBOX_KEY=your_sandbox_key
AUTHY_SANDBOX=false

Register the provider and the Authy facade in config/app.php if you're on a Laravel version predating package auto-discovery. Then start with a one-file smoke test in routes/web.php (or tinker) to confirm credentials before touching your auth flow:

use Rinvex\Authy\Facades\Authy;

$user = Authy::register('[email protected]', '5555555555', '1'); // email, phone (digits only), country code
$authyId = $user->id();                                        // persist this
Authy::requestSms($authyId);
Authy::verify($authyId, $token);

Where to look first

  • src/Authy.php — the whole package. Each public method is a thin 1:1 mapping onto an Authy REST endpoint (/protected/json/users/new, /verify/{token}/{id}, /sms/{id}, /call/{id}, /users/{id}/status, /users/{id}/secret, DELETE /users/{id}). Read it top to bottom; it takes ten minutes and tells you everything the README doesn't.
  • config/rinvex.authy.php — key selection, sandbox toggle, and any base-URI override.
  • The facade class — the definitive method list.

First use case: enrolment at signup. The package deliberately does not do this for you. The pattern is: after a user registers, call register(), store the returned Authy ID on your users row, immediately requestSms(), then gate the account on a successful verify().

Implementation Patterns

Persist the Authy ID, and treat it as the identity. Add a migration with authy_id (unsigned big integer, nullable, indexed) plus two_factor_enabled / phone_verified_at timestamps. Everything downstream keys off that integer, not off the phone number — the phone can change; the Authy user ID doesn't.

Enrolment as an action class. Wrap the three calls in something like App\Actions\Authy\EnrolUser so the controller stays thin and you have one place to add retry logic, event dispatch, and audit logging:

public function __invoke(User $user, string $phone, string $countryCode): void
{
    if ($user->authy_id) {
        throw new RuntimeException('Already enrolled — re-registering mints a new Authy ID.');
    }

    $response = Authy::register($user->email, preg_replace('/\D/', '', $phone), $countryCode);
    $user->forceFill(['authy_id' => $response->id()])->save();

    Authy::requestSms($user->authy_id);
    event(new TwoFactorChallenged($user, 'sms'));
}

Login flow integration. Don't try to wedge this into Laravel's LoginController. Do the password check normally but withhold Auth::login() until the second factor passes; stash a signed session flag (2fa.pending_user_id + expiry) between the two requests. That keeps auth middleware honest and avoids the classic bug of a half-authenticated session.

Verification middleware for step-up. Once 2fa is confirmed at login, add an EnsureTwoFactorVerified middleware for sensitive routes (billing, API token creation) that re-challenges rather than relying on the login-time check.

Throttle the challenge endpoints yourself. requestSms() / requestCall() are the expensive, abusable calls. Put a RateLimiter::attempt("authy-sms:{$user->id}", 3, 300) in front of the resend button and queue the actual request (dispatch(...)->onQueue('authy')) so a slow Authy response never blocks a page render.

TOTP/QR as an alternative factor. Authy::secret($authyId) returns the shared secret (and a QR URI) so you can render an authenticator-app flow alongside SMS. If you expose this, it's a one-time enrolment screen behind re-authentication — never an endpoint a session can hit repeatedly.

Disable and delete. On "turn off 2FA", do not call delete() unless the user is leaving entirely — deleting the Authy user destroys the ID and forces re-enrolment from scratch. On account deletion, do call Authy::delete($authyId) so the phone number isn't retained by the vendor (GDPR/CCPA).

Abstract it behind an interface from day one. Bind TwoFactorProvider in the container and implement it with this package now, Twilio Verify later. This is five lines of work today and a full afternoon saved when the vendor changes.

Gotchas and Tips

Authy tokens are 7 digits, not 6. This trips up everyone who's used Google Authenticator. Validate with /^\d{7}$/ (allow 6–8 if you want to be forgiving across configurations), and size your input field accordingly. Nothing is more frustrating than a correct code rejected by your own digits:6 rule.

The package has no fakes, and it isn't built on Laravel's HTTP client. Http::fake() will not intercept these calls — the wrapper uses Guzzle directly. In tests, bind a mock Authy implementation (or a fake TwoFactorProvider) into the container. Reserve real sandbox calls for a single integration test.

Sandbox keys behave differently. The sandbox accepts any well-formed token in many cases and won't deliver real SMS. Don't let a green sandbox test convince you the flow works end to end; do one production walkthrough with a real phone before shipping.

Argument order is not consistent across methods. register() takes (email, phone, countryCode); verify() takes the Authy ID and token in a different relative order than you'd guess. Always pass named variables and re-read the signature in src/Authy.php rather than relying on muscle memory — a swapped ID/token pair fails silently and confusingly.

Country codes are digits only. "1", not "+1" or "001". Phone numbers likewise: strip +, spaces, dashes, and parentheses before handing them over, or Authy will reject the registration with a generic validation error.

Every method returns a wrapper object, not a boolean. You need to inspect the body (->succeed() / the decoded JSON) rather than truthy-checking, because a failed call can still be a non-throwing object. Wrap every call in a try/catch for the package's exception class and convert it into a user-facing message — never render the raw Authy error back to the browser, it can leak account state.

Authy throttles hard and returns 429s. Blind retries make it worse. Catch the rate-limit response, disable the resend button with a countdown in your UI, and log it. Keep a per-user cooldown in your own DB too so you're not dependent on the vendor telling you.

Config caching bites multi-tenant setups. If different tenants have different Authy accounts, config()->set() at runtime only works if the client hasn't been resolved yet — app()->forgetInstance(Authy::class) between tenants, or bind a per-tenant client. With php artisan config:cache active, .env changes require a re-cache.

This is a wrapper, not a solution. No migrations, no user model trait, no middleware, no recovery-code generator. Budget for building backup codes yourself (and store them hashed — they're passwords).

Vendor risk is real. Twilio has signalled end-of-life for the Authy API in favour of Verify. Before committing this to a production auth path, check the current deprecation timeline. That's an argument for the interface abstraction above, not for skipping it — but pin the package version, and don't build anything else on top of Authy-specific response shapes.

Don't log tokens or the API key. Add both to your config/logging.php don't_log/sanitisers list, and scrub request bodies in Sentry/Horizon if you dispatch to queues. A 7-digit code in a log file is a live second factor.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route