betterauth/multimodal-php
Framework-agnostic PHP authentication with session, Paseto V4 token, or hybrid modes. Includes OAuth social login, OpenID Connect, magic links, TOTP 2FA, risk-based session scoring, multi-tenancy, plugins, and PDO storage.
id (generated with Crypto::randomToken(16), distinct from the token) so it can be
listed and revoked by a non-secret handle. Session::getId()/setId(),
SimpleSession::fromArray()/toArray() (now carry id), and
SessionService::getById()/deleteById() were added.SessionAuthManager::revokeSession($userId, $sessionId) now resolves the session by its
opaque id instead of the token, and resolves via findById() (not validate()) so an
expired session stays revocable. Ownership and missing-session behaviour are unchanged.TotpProvider accepts an optional
RateLimiterInterface; verify()/verifyAndEnable() throttle attempts (keyed on
totp:verify:{userId}) when one is supplied.AuthManager::verifyCredentials() + completeSignIn(). Credential verification and
auth-result creation are now separate single-source primitives, so a caller can gate
(e.g. require 2FA) between verifying the password and creating the session/tokens without
verifying the password twice.PdoTotpStorage now persists the TOTP algorithm, so the SHA-1 → SHA-256 migration state
survives storage round-trips (it was previously always read back as SHA-1).SessionRepositoryInterface gains two methods: findById(string $id): ?Session and
deleteById(string $id): bool. Any custom implementation must add them.PdoSessionRepository::createTable() now creates an id VARCHAR(64) NOT NULL column with a
UNIQUE constraint. Since the DDL is CREATE TABLE IF NOT EXISTS, existing PDO
deployments are not migrated automatically — add the column manually, e.g.
ALTER TABLE sessions ADD COLUMN id VARCHAR(64) NULL, ADD UNIQUE KEY uniq_id (id);
then backfill ids for current rows. (Doctrine/Symfony migration is handled separately.)SessionAuthManager::signUp() writes the hashed password under the canonical
password_hash key (was password), so user creation through PdoUserRepository no
longer persists a NULL password.requires2fa: removed a dead 24-hour branch (DateInterval::$h is always < 24) so
the elapsed-time check is correct.refresh_token;
it returns token_type: session instead.INTEGER PRIMARY KEY AUTOINCREMENT with INT AUTO_INCREMENT PRIMARY KEY in
PdoTotpStorage and PdoEmailVerificationStorage, and the SQLite/PostgreSQL
ON CONFLICT … DO UPDATE upsert in PdoTotpStorage::store() with MySQL
ON DUPLICATE KEY UPDATE. Previously the DDL mixed dialects and ran cleanly on no single
engine. For SQLite/PostgreSQL, supply your own storage implementation.PdoTotpStorage::useBackupCode()
compared the submitted code against stored hashes with in_array(), which always
failed — backup codes (and 2FA recovery/disable) were effectively unusable. They are
now verified with password_verify().PdoTotpStorage accepts an optional master key
(3rd constructor argument) and encrypts the TOTP seed with AES-256-GCM. A DB read no
longer exposes usable TOTP secrets.PdoSessionRepository and
PdoRefreshTokenRepository store sha256(token) (and replaced_by), look up by hash,
and re-inject the plaintext token on the returned entity. A DB read no longer yields
usable tokens.PdoUserRepository::findByProvider() now validates the
provider name against an allowlist before interpolating it into the JSON_EXTRACT path.OAuthManager::handleCallback() requires a matching state (an attacker can no longer
skip CSRF verification by omitting the parameter).plain removed. Only S256 is accepted in AuthorizationCode::verifyChallenge()
and advertised in the OIDC discovery document.Crypto::encrypt() / Crypto::decrypt() — authenticated encryption (AES-256-GCM) with an
enc:v1: version marker; decrypt() returns unmarked legacy values unchanged for safe
migration.token values in place (token = sha256(token)) to preserve active sessions.PdoTotpStorage. Existing plaintext secrets keep working (legacy passthrough) and are
encrypted on their next store(). Keep the master key stable — rotating it makes existing
encrypted secrets unreadable.First public release of the framework-agnostic authentication core for PHP.
user.created, token.created, session.created, …)json, openssl, pdocomposer require betterauth/multimodal-php
How can I help you explore Laravel packages today?