Search by

omniscient / omniauth

GlitchArt

Omniauth: who signs in, and by which factors, under any PHP host - the contracts (accounts, the stores of each proof, the second-factor rule, the host's small duties), the conductor (begin, then verify; the attempt, the result), the methods that need no library (password through symfony/password-has

Package info

github.com/glitchr-studio/omniauth

pkg:composer/omniscient/omniauth

Statistics

Installs: 76

Dependents: 6

Suggesters: 1

Stars: 0

Open Issues: 0

1.x-dev 2026-10-21 01:30 UTC

This package is auto-updated.

Last update: 2026-10-21 01:31:37 UTC


README

Who signs in, and by which factors, under any PHP host: the methods and their ceremonies (begin, then verify), the second-factor rule, and the result of an attempt - who, what passed, what remains. The host keeps its firewall or guard, the request and the response, its session cookie and its pages; the application keeps its accounts and their proofs in its own tables.

use Omniauth\{ArrayState, CacheThrottle, Context, Omniauth};
use Omniauth\Method\Methods;
use Omniauth\Password\PasswordMethod;
use Omniauth\EmailCode\EmailCodeMethod;
use Omniauth\BackupCode\BackupCodeMethod;
use Omniauth\TrustedDevice\TrustedDevices;

$omniauth = new Omniauth(
    accounts: $accounts,                                  // your accounts, behind Contract\Accounts
    methods: new Methods([
        new PasswordMethod($accounts, $passwords),        // symfony/password-hasher: Symfony's hashes, >72 bytes included
        new EmailCodeMethod($accounts, $emailCodes, $sender, $secret),
        new BackupCodeMethod($backupCodes),
        new TrustedDevices($accounts, $trustedVersion, $secret),   // scheb/2fa's trusted_device cookie
    ]),
    state: new ArrayState($_SESSION),                     // the session, under a framework
    throttle: new CacheThrottle($pool),                   // any PSR-6 pool or PSR-16 cache
);
$context = new Context(origin: 'https://example.org', host: 'example.org', ip: $_SERVER['REMOTE_ADDR'], trustedDevice: $_COOKIE['trusted_device'] ?? null);

$result = $omniauth->verify('password', $context, ['identifier' => 'ringo@example.org', 'password' => '...']);
// SecondFactorRequired, $result->offered === [Factor::EmailCode, Factor::BackupCode]
$omniauth->begin('email-code', $context);                 // the code goes out through your Sender
$result = $omniauth->verify('email-code', $context, ['code' => '481516', 'trust' => true]);
// Authenticated, $result->passed === [Factor::Password, Factor::EmailCode], $result->trustDevice === 'eyJ0eXAiOiJKV1Qi...'

It requires PHP 8.2, four PSR interfaces (cache, simple-cache, clock, event-dispatcher) and symfony/password-hasher, a standalone component. The methods that carry a library are packages of their own; the hosts are bridged in omnibridge:

Package What
omniscient/omniauth (here) the contracts, the conductor, the methods that need no library: password, sign-in link, code by e-mail, backup codes, trusted devices; events; Testing\
omniauth/passkey passkeys, over web-auth/webauthn-lib; compatible both ways with web-auth/webauthn-symfony-bundle's records
omniauth/totp authenticator applications, over spomky-labs/otphp, on scheb/2fa's parameters; a code once
omniauth/oauth sign-in through identity providers, with no OAuth library: the state, PKCE and nonce, the linking by sub, the sign-up of an unknown identity
omniauth/google Google, over league/oauth2-google: one package per provider
omnibridge/symfony, omnibridge/laravel an authenticator per firewall; a guard and a user provider

The name: OmniAuth is also Ruby's well-known sign-in library. This one is PHP, under omniscient/, and unrelated.

The shape

Omniauth the conductor: begin(), verify(), attempt(), abandon(), enrol(), revoke() - one sequence whatever the method: throttle, Verifying, the method, Accounts::check(), the second-factor rule, SignedIn or SignInFailed
Attempt, Result, Status, Failure, Factor, Context, Proof, Enrolment, Enrolled, Message, PendingIdentity the attempt kept between two requests, what it ended with, the stable keys a page translates
Contract\ Accounts; one store per proof (Passwords, LinkTokens, EmailCodes, BackupCodes, TrustedVersion, TotpSecrets, Identities); SecondFactorPolicy; the host's duties (State, Throttle, Sender)
Method\ Method (verify), Challenging (begin first), SecondFactor (enrolled?), Ambient (read from the context), Enrolling, Revocable; Verdict, Challenge, Methods
Password\, Link\, EmailCode\, BackupCode\, TrustedDevice\ the five methods of the core
EnrolledSecondFactors, ArrayState, CacheThrottle, SystemClock the defaults: the rule, the state, the throttle, the clock
Event\ PSR-14: Beginning, Verifying, SecondFactorRequired, SignedIn, SignInFailed, AccountRefused, Revoking, CredentialAdded, CredentialRemoved
Testing\ in-memory stores, a recording sender, a fixed clock, an array cache, stand-ins for a passkey and an authenticator application

Decisions it carries (Marco, 2026-10-20)

  • No second factor after a passkey (QA1), a second factor after an identity provider (QA2): EnrolledSecondFactors::AFTER is [Password, Link, OAuth].
  • The sign-in link: 15 minutes, once, its SHA-256 kept, never its value; a new link at each request, 3 an hour (QA4).
  • The code by e-mail kept hashed: an HMAC keyed by the host's secret, bound to the account (QA5).
  • The bridges in omnibridge, omniauth only suggested there (QA8).

Documentation

Verified, and not

  • Verified (docker compose run --rm omniauth test in docker/, PHP 8.4; every file linted on PHP 8.2): each method's way through and its refusals on the in-memory stores - wrong secret, unknown address answered as a wrong password, expired, used twice, throttled, a banned account, the second factor offered, passed, not offered, its attempt expired; QA1 and QA2 through the default rule; the trusted-device tokens equal to those scheb/2fa 8.6.1's JwtTokenEncoder writes for the same input, whole seconds and microseconds, each read by the other (recorded, and live with scheb/2fa installed in the harness); hashes written by symfony/password-hasher 8.1 ("auto": bcrypt, cost 13), one of a 102-byte password that password_verify() alone refuses; docs/installation.md run as written; in plain PHP, a whole sign-in by hand loading nothing but omniauth, the PSR interfaces and symfony/password-hasher, and every class of the package loading with them alone (BareTest).
  • Not verified: an application's own stores (omnibase fills them at step AU4); a real host (the bridges come with AU6 and AU8); a cookie written by a running scheb/2fa on an application (the encoder is compared, not a browser's cookie); concurrent tries against CacheThrottle (read then written, not atomic: a host that needs it exact uses its rate limiter).

Licence

MIT.