twofas/account-sdk
A PHP SDK for integrating 2FAS account services into Laravel or plain PHP applications. It offers a simple client for authentication and account operations, reducing boilerplate when connecting your app to the 2FAS platform.
Install:
composer require twofas/account-sdk
php artisan vendor:publish --tag=twofas-config # if a ServiceProvider ships; otherwise skip
Where to look first, in this order:
README.md — this package is thin and community-maintained, so the README is the real spec, not the docblock.src/Client.php (or src/AccountSdk.php) — the constructor shows what config it expects (base URL, client credentials, HTTP client injection).src/Http/ or src/Endpoints/ — each class maps one API surface area (auth, devices, tokens, sync).tests/ — the most reliable documentation for request shapes and expected responses.First use case — the reason you'd reach for this package at all: authenticate a user against the 2FAS Account API and pull their synced authenticator state. The minimal happy path is:
$client = new \TwoFas\AccountSdk\Client([
'base_uri' => config('services.twofas.base_uri'),
'http_client' => app(\GuzzleHttp\Client::class), // if PSR-18/PSR-17 compatible
]);
$auth = $client->auth()->login($email, $password); // returns token pair
$devices = $client->devices()->list($auth->accessToken());
If the package is not PSR-aware and ships its own Guzzle dependency, resolve it from the container anyway so you can swap the handler in tests. Do not new it inline throughout your app — you will regret it the first time the base URI changes or the token lifetime bites you in a queue worker.
Bind the client as a singleton, not a facade dependency. There is no stable facade here. Put it behind your own interface:
// app/Services/TwoFas/TwoFasGateway.php
final class TwoFasGateway implements Contracts\TwoFasGateway
{
public function __construct(private \TwoFas\AccountSdk\Client $client) {}
public function login(string $email, string $password): TokenPair { /* … */ }
}
Then $this->app->singleton(Contracts\TwoFasGateway::class, fn ($app) => new TwoFasGateway(new Client(config('services.twofas'))));. This gives you two wins: you can fake the gateway in feature tests without touching HTTP, and you can pin the SDK version behind an interface if the API drifts.
Separate the token lifecycle from the call sites. The API is token-based; almost every bug in this integration is a stale-token bug. Centralize it:
if ($token->expiresAt->isPast()) {
$token = $gateway->refresh($token->refreshToken);
// persist, and emit an event so other consumers pick it up
}
For Laravel, store tokens in an encrypted model column (encrypted cast) on your users table or a twofas_accounts table — not in session, because jobs and console commands need them too.
Wrap multi-step flows in a DTO-returning action class. Registration typically is: create account → verify email → register device → initial sync. Model each step as a small action with idempotency where the API allows it, and return typed DTOs (Account, Device, TokenPair) instead of raw arrays. The SDK's response objects are typically thin; map them into your own so you are not coupled to their array shapes.
Integration tips specific to 2FA state: treat synced tokens as opaque blobs. Do not parse them, do not assume a shape — you only need to store and re-upload them. If you also want to display codes (not usually the point of an SDK like this), that is a different library.
Testing: since the SDK is HTTP-bound, test at the seam. Use Http::fake() if it leverages the Laravel HTTP client, otherwise inject a PSR-18 mock into the constructor. Assert the request you build (headers, path, JSON body), not the SDK's response parsing — that is the SDK author's problem.
"twofas/account-sdk": "^1.2") and commit the lock file. When the upstream API changes, the SDK may or may not follow — you want upgrades to be an explicit decision, not a deploy surprise.connect_timeout (2s) and timeout (5–10s); the library defaults are usually too generous for web requests.ClientInterface in the constructor (swap for mocking), a customizable User-Agent, and whether endpoints are separate classes or methods on one god-object. The former lets you add endpoints without forking; the latter does not.AppServiceProvider::register() and move on.How can I help you explore Laravel packages today?