Lue dans les fichiers PHP du paquet au commit 66aa549da28c : ses espaces de noms, classes, interfaces et énumérations, leurs constantes, propriétés et méthodes publiques avec leur PHPDoc, et les options que prennent ses fabriques. Rien n’en est exécuté.
ArrayStateclassThe visitor's state in a PHP array: plain PHP (hand it $_SESSION by reference, or keep the array yourself) and tests. Symfony and Laravel keep it in their session (omnibridge/symfony, omnibridge/laravel).
6 methods
AttemptclassOne visitor's attempt to sign in, kept by the host between two requests (Contract\State): the account found by the first factor, the factors passed, the second factors offered, and what a ceremony keeps between its beginning and its verification (a challenge, a code's deadline) - never in the response.
14 methods
CacheThrottleclassA fixed window per scope and key, counted in any PSR-6 pool or PSR-16 cache: plain PHP and tests. Under Symfony and Laravel, their rate limiters answer the same contract.
3 methods · 1 constant
ContextclassWhat the host knows of the request, with no object of its framework: the same values under Symfony, Laravel and plain PHP.
1 method · 6 properties
EnrolledclassWhat setting a factor up ended with; the secrets to show once (backup codes) when it worked.
4 methods · 3 properties
EnrolledSecondFactorsclassThe core's second-factor rule: an account that set up a second factor (an authenticator application, the code by e-mail) must pass one of them - or a backup code - after a first factor of $after; a second factor already passed (a trusted device among them) is enough.
2 methods · 1 constant
EnrolmentclassSetting a factor up for an account, in the same two steps as a sign-in: begin() (a code sent, a secret, options to sign, or nothing), confirm() (what the visitor sent back; the secrets to show once).
3 methods · 2 properties
FactorenumWhat a person proves when signing in, named once for every method, every policy and every host: the stable key a host's routes, settings and translations use.
2 methods · 8 cases
FailureenumWhy an attempt failed: stable keys a page translates (omnibase's "@notifications.login.*" catalogues), never a message.
10 cases
MessageclassWhat a method asks the host to send to an account: a sign-in link, a code.
1 method · 3 properties
OmniauthclassThe conductor: who signs in, by which factors, and what remains to pass.
8 methods · 3 constants
PendingIdentityclassSomeone a provider vouched for whom no account is linked to: what a sign-up form fills in, and what links the account once it exists.
1 method · 5 properties
ProofclassWhat the visitor sent: ['identifier' => ..., 'password' => ...], ['code' => ...], ['token' => ...], a WebAuthn assertion's JSON under 'json', an OAuth return's 'code' and 'state'. Two keys mean the same to every method: 'remember' (the host sets its remember-me) and 'trust' (a trusted-device cookie after a second factor).
7 methods
ResultclassWhat a verification ended with: who, by which factors, what remains to pass, and what the host does next (open its session, set its remember-me, set the trusted-device cookie).
4 methods · 9 properties
RoleenumA first factor finds the account; a second one is asked of an account already found.
2 cases
StatusenumWhere an attempt stands after a verification.
4 cases
SystemClockclassThe machine's clock, when the host gives none (Testing\FixedClock in tests).
1 method
BackupCodeMethodclassBackup codes: handed out once (8 by default, "A1B2C3-D4E5F6" as omnibase draws them), kept as password_hash() hashes - the very hashes omnibase's user.backupCodes holds - and each good once: the one that serves is removed. They stand in for another second factor; alone, they ask for none (EnrolledSecondFactors).
8 methods
AccountsinterfaceThe application's accounts: opaque objects to omniauth, which implement nothing of it (omnibase fills this in on its UserRepository).
7 methods
BackupCodesinterfaceThe backup codes' hashes, password_hash()'s (omnibase: user.backupCodes, a JSON list).
2 methods
EmailCodesinterfaceThe code sent by e-mail (omnibase: user.emailAuthEnabled, user.emailAuthCode). The store keeps the code's keyed hash, never the code; its deadline is kept in the attempt.
6 methods
IdentitiesinterfaceThe accounts linked to an identity provider, by the person's identifier there ("sub"); read by Omniauth\OAuth\OAuthMethod (omnibase: the userIdentity table).
5 methods
LinkTokensinterfaceThe sign-in links in flight. The store sees their SHA-256, never their value: whoever reads the table cannot sign in with it.
2 methods
PasswordsinterfaceThe accounts' password hashes (omnibase: user.password).
2 methods
PlainLinkTokensinterfaceFor the switch only: the links issued before omniauth were kept in clear (omnibase's userToken "login-token"). A LinkTokens store that still holds some also implements this, and they are read as they are until they expire; once none is left, the store stops implementing it.
1 method
SecondFactorPolicyinterfaceThe second-factor rule: what an account must still pass.
1 method
SenderinterfaceSends a sign-in link or a code to an account (omnibase: its Notifier; tests: Testing\RecordingSender).
1 method
StateinterfaceThe visitor's own state between two requests: the session under Symfony and Laravel, Omniauth\ArrayState in plain PHP and tests. Values are arrays of scalars.
3 methods
ThrottleinterfaceLimits the tries: Symfony's RateLimiterFactory, Laravel's RateLimiter, Omniauth\CacheThrottle on any PSR-6 or PSR-16 cache.
2 methods
TotpSecretsinterfaceThe authenticator applications' secrets, base32 (omnibase: user.secret); read by omniauth/otp (SealedTotpSecrets seals them at rest).
3 methods
TrustedVersioninterfaceThe trusted devices' version (omnibase: user.trustedTokenVersion, scheb's TrustedDeviceInterface): a token of another version no longer counts.
2 methods
EmailCodeMethodclassA code sent by e-mail once a first factor passed: 6 digits (as scheb/2fa draws them), good for 10 minutes and once. By Marco's decision QA5 (2026-10-20) the store keeps its hash, never the code: an HMAC-SHA256 keyed by the host's secret and bound to the account, compared in constant time; the deadline is kept in the attempt.
9 methods · 1 constant
AccountRefusedclassThe proof was good but the account may not sign in (omnibase: the "welcome back" e-mail of a disabled account).
1 method · 4 properties
BeginningclassBefore a ceremony begins (a link or a code is about to be sent): a listener may refuse.
3 methods · 4 properties
CredentialAddedclassA factor was set up, or a password changed.
1 method · 2 properties
CredentialRemovedclassA factor was turned off; the trusted devices fell back with it.
1 method · 2 properties
RevokingclassBefore a factor is turned off: omnibase's rule ("the administrator wins": a role that requires a second factor keeps it) refuses here, so a hidden button and a forged POST meet the same predicate.
3 methods · 2 properties
SecondFactorRequiredclassA first factor passed and a second one is due.
1 method · 4 properties
SignInFailedclassA verification failed: logged with the address and the factor, never with the secret; SignInGuard counts it.
1 method · 6 properties
SignedInclassSigned in: omnibase's UserTracker, the "new device" e-mail, the account brought up to date listen here.
1 method · 3 properties
VerifyingclassBefore a method checks a proof (and after the throttle let it through): a listener may refuse - omnishield when the visitor is not a human, omnibase's SignInGuard when a captcha is due.
3 methods · 4 properties
AccountRefusedclassThrown by Accounts::check(): the account may not sign in now, for a stable reason the page translates.
1 method · 1 property
OmniauthExceptioninterfaceEvery exception omniauth throws.
UnknownMethodclassA factor no method was mounted for: a host's mistake, not a visitor's.
1 method
LinkMethodclassA sign-in link sent to the account's address, by Marco's decision QA4 (2026-10-20): good for 15 minutes, once, and kept hashed (SHA-256) - the store never sees its value. Each request sends a new link (the conductor limits them: "link.begin", 3 an hour by default), and the answer is the same whether the address has an account or not.
5 methods · 1 constant
AmbientinterfaceA second factor the visitor does not present: it is read from the context (the trusted-device cookie). The conductor tries it by itself when a second factor is required, and asks it what to leave on the browser when the visitor asked to be trusted after passing another one.
1 method
ChallengeclassWhat the host gives back after a beginning: JSON for a script, an address to go to, "it was sent", or why not.
6 methods · 5 constants
ChallenginginterfaceA method that first gives something to the browser or the mailbox: options to sign, an address to go to, a link or a code sent. What it must remember goes into the attempt (Attempt::keep()), never into the challenge.
1 method
EnrollinginterfaceA method an account sets up from its settings, in the same two steps as a sign-in. The attempt is the enrolment's own (its account identified), kept apart from any sign-in.
2 methods
MethodinterfaceA way to sign in: it checks a proof within an attempt, and touches neither request nor response.
2 methods
MethodsclassThe methods a host mounted, one per factor.
8 methods
RevocableinterfaceA factor an account can turn off (or, for trusted devices, forget every browser).
1 method
SecondFactorinterfaceA second factor an account sets up: the policy asks it whether this one has.
1 method
VerdictclassWhat a method concluded of a proof.
5 methods
AuthorizationclassWhat one trip to a provider is bound to, drawn at the beginning and kept in the attempt (never in the answer): the state the return must bring back (against a forged return: login CSRF), the PKCE verifier whose S256 challenge goes in the authorization address (against a code stolen or injected: only the one who began can exchange it, RFC 7636), and the nonce an ID token must carry (OpenID Connect, against a token replayed) - and the origin of the request that began the trip, against which a provider configured with a return path ("/login/google/callback") makes its return address: the host the visitor is on at that request, not the first one a long-running worker served, and the same address again at the exchange (RFC 6749 §4.1.3: the token request repeats the authorization's).
8 methods · 4 properties
IdentityclassWhom a provider vouched for, read from its ID token or its userinfo - never from the return address's parameters.
3 methods · 6 properties
NoSecondFactorLinkingclassQA3's rule for the address: an account that set up a second factor (an authenticator application, the code by e-mail) is not linked by its address - whoever holds the provider's account would then be one factor away from it for good. It reads the methods mounted, so it can be built before the OAuth method is added to them.
2 methods
OAuthMethodclassSigning in through an identity provider (OAuth 2, OpenID Connect), a first factor: begin() draws a state, a PKCE verifier and a nonce, keeps them in the attempt and answers with the provider's authorization address (Challenge::redirect()); verify() takes the return's ['code' => …, 'state' => …] (or its ['error' => …]), takes what the attempt kept - once: a return comes back once -, compares the state in constant time, has the provider exchange the code with the verifier and read the person (an ID token or a userinfo, never the address's parameters), then finds the account (Marco's decision QA3):
8 methods
EmailLinkinginterfaceMay an account be linked to an identity by its address alone - the provider having said the address is verified, and no account linked to that identity yet? Marco's decision QA3: never for an account that has a second factor or an administration role (NoSecondFactorLinking says the first; omnibase's rule, step AU4, adds its roles); the person then signs in otherwise and links the provider from the account's settings.
1 method
ProviderErrorclassThe provider did not vouch for anyone: the code refused at the exchange, an ID token not for this client, past its time or without the nonce, a profile without its subject. $reason is the stable word the method answers with; the message (and the previous exception: league's, Guzzle's) is for the server's log, not the page.
1 method · 1 property · 4 constants
LeagueProviderclassA league/oauth2-client provider as omniauth's Provider - the ground of the provider packages built on league (omniauth/google's GoogleProvider), and any other league provider by hand (league/oauth2-facebook, a GenericProvider on a provider's three addresses...) until it has a package of its own. league/oauth2-client is not required by omniauth: the provider packages require it (this class loads without it, and is only usable with it). The authorization address with the state and the S256 challenge omniauth drew, the code exchanged with its verifier, the person read from the resource owner's details through the access token. $fields names the details' keys: the subject (its resource owner's getId() when null), the address, whether it is verified (null: the provider says nothing, so it is not - no account is ever linked by such an address), the name.
5 methods · 1 constant
ProviderinterfaceAn identity provider: where to send the visitor, then whom the code that came back vouches for. Its return address is its own, fixed at its configuration (never taken from a request).
3 methods
ProviderFactoryinterfaceMakes the providers of one kind from a host's configuration: each provider package has its own (omniauth/google's GoogleFactory), so this package needs no OAuth library. Providers::configured() asks each factory in turn.
1 method
ProvidersclassThe providers a host serves, by name. Marco's decisions: Google alone today (QA7), and one package per provider (2026-10-21) - omniauth/google first; configured() makes them through each package's factory.
8 methods
FakeProviderclassAn authorization server in memory, for a host's tests and a first try: it answers the authorization address as a provider does once the person consented (consent(): the return's code and state), and exchanges a code as a provider does - once, for the client and the return address it was given to, and only with the PKCE verifier whose S256 challenge the authorization carried (RFC 7636 §4.6); the nonce is the one the address carried. So a test signs in with no network, and a forged or injected return fails here as it would at Google.
7 methods · 3 properties · 1 constant
PasswordMethodclassThe password, checked by symfony/password-hasher under every host - never by password_verify() alone: Symfony prehashes a password longer than 72 bytes (or holding a NUL byte) before bcrypt, so password_verify(), and Laravel's Hash with it, would refuse those accounts. The same hasher says when to rehash (cost 13 here, 12 by Laravel's default: without it, each sign-in on the other host would rehash).
5 methods
ArrayCacheclassA PSR-16 cache in a PHP array, its lifetimes read on the clock given: what CacheThrottle counts in for tests and a first try, with no cache library.
9 methods
FixedClockclassA clock that stands still until told: deadlines and windows tested to the second.
4 methods
InMemoryEmailCodesclassThe e-mailed codes on TestAccount objects (InMemoryStore::emailCodes()).
6 methods
InMemoryStoreclassEvery store contract in memory, over TestAccount objects: tests, the harness's bare script, a first try in plain PHP. An application fills the same contracts on its tables (omnibase: Base\Auth\). The e-mailed codes and the TOTP secrets, whose contracts share method names, are emailCodes() and totpSecrets(), over the same accounts.
25 methods
InMemoryTotpSecretsclassThe TOTP secrets on TestAccount objects (InMemoryStore::totpSecrets()), for omniauth/otp's tests.
3 methods
ProviderContractTeststraitWhat every identity provider does, as PHPUnit tests: a provider package's test case uses this trait, gives the provider and a way to make its server answer (recorded answers: no test reaches the network), and the suite checks the Provider contract:
5 methods
RecordingSenderclassSends nothing, keeps every message: the link or the code a test reads back.
3 methods · 1 property
StubMethodclassA first factor that passes for the account of a handle: a passkey or an identity provider without their library, so a host's tests (and the core's) drive the conductor and the policy. Proof: ['handle' => ...].
3 methods
StubSecondFactorclassA second factor (an authenticator application without otphp) enrolled for the handles given, passing on the code given. Proof: ['code' => ...].
4 methods
TestAccountclassAn account for tests and first steps, every proof on it: what InMemoryStore reads and writes.
1 method · 15 properties
TrustedDevicesclassA second factor already passed on this browser: the trusted_device cookie, written in scheb/2fa's very format so the devices trusted before the switch stay trusted - JWTs signed HS256 by the host's secret (Symfony's kernel.secret), claims iat, exp, "usr" (the account's identifier), "fwl" (the firewall), "vsn" (the account's trusted version), several tokens joined by ";". Encoded with PHP's own HMAC, byte for byte what lcobucci/jwt writes for scheb's JwtTokenEncoder (dates with their microseconds), and read as it reads them (signature, iat <= now < exp).
8 methods · 2 constants
Omniauth
ArrayState class
final class ArrayState implements State ArrayState.php:12
The visitor's state in a PHP array: plain PHP (hand it $_SESSION by reference, or keep the array yourself) and tests. Symfony and Laravel keep it in their session (omnibridge/symfony, omnibridge/laravel).
ArrayState::__construct()
public function __construct(array &$values = [])ArrayState::session()
public static function session(): selfOver $_SESSION (session_start() first): the attempt lives as long as the PHP session.
ArrayState::get()
public function get(string $key): mixedArrayState::set()
public function set(string $key, mixed $value): voidArrayState::remove()
public function remove(string $key): voidArrayState::all()
public function all(): arrayAttempt class
final class Attempt Attempt.php:15
One visitor's attempt to sign in, kept by the host between two requests (Contract\State): the account found by the first factor, the factors passed, the second factors offered, and what a ceremony keeps between its beginning and its verification (a challenge, a code's deadline) - never in the response.
It holds the account's handle, not the account: the conductor loads the account again (Accounts::byHandle()) each time it reads the attempt back.
Attempt::__construct()
public function __construct(readonly int $startedAt, ?string $handle = null, array $passed = [], array $offered = [], array $kept = [], bool $remember = false)Attempt::startedAt()
public function startedAt(): intAttempt::account()
public function account(): ?objectThe account the first factor found, or the account an enrolment is for.
Attempt::handle()
public function handle(): ?stringAttempt::passed()
public function passed(): arrayAttempt::hasPassed()
public function hasPassed(Factor $factor): boolAttempt::offered()
public function offered(): arrayAttempt::remembers()
public function remembers(): boolAttempt::keep()
public function keep(Factor|string $key, string|int|float|bool|array|null $value): voidKeeps what a ceremony needs at its verification (scalars and arrays: the attempt is serialised).
Attempt::kept()
public function kept(Factor|string $key): string|int|float|bool|array|nullAttempt::take()
public function take(Factor|string $key): string|int|float|bool|array|nullReads what was kept and forgets it: a challenge serves once.
Attempt::restart()
public function restart(int $now): selfA new attempt that keeps only what ceremonies kept (a challenge begun before this first factor).
Attempt::toArray()
public function toArray(): array- Returns
what Contract\State keeps
Attempt::fromArray()
public static function fromArray(mixed $data): ?self$datawhat Contract\State gave back: null (or anything malformed) is no attempt
CacheThrottle class
final class CacheThrottle implements Throttle CacheThrottle.php:18
A fixed window per scope and key, counted in any PSR-6 pool or PSR-16 cache: plain PHP and tests. Under Symfony and Laravel, their rate limiters answer the same contract.
Read then written, not atomic: two requests at the same instant may both pass the last try. A host that needs it exact uses its rate limiter.
const LIMITS = ['password' => [5, 60], 'link.begin' => [3, 3600], 'email-code' => [5, 900], 'email-code.begin' => [3, 900], 'backup-code' => [5, 900], 'totp' => [5, 900], '…Tries per window, in seconds. "*": any other scope. "<factor>.begin" limits what sends something (a link, a code).
CacheThrottle::__construct()
public function __construct(readonly CacheItemPoolInterface|CacheInterface $cache, array $limits = [], ?ClockInterface $clock = null, readonly string $prefix = 'omniauth.throttle.')$limitsreplace the defaults scope by scope
CacheThrottle::consume()
public function consume(string $scope, string $key): boolCacheThrottle::reset()
public function reset(string $scope, string $key): voidContext class
final readonly class Context Context.php:9
What the host knows of the request, with no object of its framework: the same values under Symfony, Laravel and plain PHP.
public string $originhttps://lesringostars.localhost:8443: WebAuthn compares the origin.
public string $hostThe relying party's identifier depends on it.
public ?string $ipThrottling keys, devices.
public ?string $userAgentpublic ?string $trustedDeviceThe trusted_device cookie, as the host read it.
public string $firewallThe trusted-device tokens' "fwl" claim (scheb/2fa's firewall name).
Context::__construct()
public function __construct(public string $origin = '', public string $host = '', public ?string $ip = null, public ?string $userAgent = null, public ?string $trustedDevice = null, public string $firewall = 'main')Enrolled class
final readonly class Enrolled Enrolled.php:6
What setting a factor up ended with; the secrets to show once (backup codes) when it worked.
public ?Failure $failurepublic array $secretspublic ?string $reason
Enrolled::__construct()
public function __construct(public ?Failure $failure = null, public array $secrets = [], public ?string $reason = null)Enrolled::done()
public static function done(array $secrets = []): selfEnrolled::failed()
public static function failed(Failure $failure, ?string $reason = null): selfEnrolled::isDone()
public function isDone(): boolEnrolledSecondFactors class
final class EnrolledSecondFactors implements SecondFactorPolicy EnrolledSecondFactors.php:23
The core's second-factor rule: an account that set up a second factor (an authenticator application, the code by e-mail) must pass one of them - or a backup code - after a first factor of $after; a second factor already passed (a trusted device among them) is enough.
$after holds Marco's decisions of 2026-10-20: QA1, no second factor after a passkey (it already proves the device and the gesture that unlocks it: Passkey is not in the list); QA2, a second factor after an identity provider (OAuth is). Backup codes alone ask for nothing: they stand in for another second factor, as in scheb/2fa.
omnibase decorates it with its SecurityPolicy (a role that requires a second factor and has none is sent to enrol; the demo account never is).
const AFTER = [Factor::Password, Factor::Link, Factor::OAuth]
EnrolledSecondFactors::__construct()
public function __construct(readonly Methods $methods, readonly array $after = self::AFTER)$afterthe first factors a second one follows
EnrolledSecondFactors::required()
public function required(object $account, array $passed, Context $context): arrayEnrolment class
final readonly class Enrolment Enrolment.php:12
Setting a factor up for an account, in the same two steps as a sign-in: begin() (a code sent, a secret, options to sign, or nothing), confirm() (what the visitor sent back; the secrets to show once).
public Factor $factorpublic object $account
Enrolment::__construct()
public function __construct(Omniauth $omniauth, public Factor $factor, public object $account)Enrolment::begin()
public function begin(Context $context, Proof|array $proof = []): ChallengeEnrolment::confirm()
public function confirm(Context $context, Proof|array $proof = []): EnrolledFactor enum
enum Factor Factor.php:10
What a person proves when signing in, named once for every method, every policy and every host: the stable key a host's routes, settings and translations use.
case Password = 'password'What one knows.
case Link = 'link'The mailbox: a link sent to the account's address.
case Passkey = 'passkey'The device and the gesture or the code that unlocks it (omniauth/webauthn).
case OAuth = 'oauth'An identity provider (Omniauth\OAuth\OAuthMethod).
case Totp = 'totp'The authenticator application (omniauth/otp).
case EmailCode = 'email-code'A code sent to the account's address once a first factor passed.
case BackupCode = 'backup-code'One of the codes handed out once, each good once.
case TrustedDevice = 'trusted-device'A second factor already passed on this browser: the trusted_device cookie.
Factor::role()
public function role(): RoleWhat it can be: a first factor (it finds the account) or a second (the account is known).
Factor::of()
public static function of(self|string $factor): selfFailure enum
enum Failure Failure.php:9
Why an attempt failed: stable keys a page translates (omnibase's "@notifications.login.*" catalogues), never a message.
case BadCredentials = 'bad-credentials'No such account, or not that secret: one answer for both.
case BadCode = 'bad-code'A code (e-mailed, backup, from an application) that is not the right one.
case InvalidLink = 'invalid-link'A sign-in link unknown, already used or past its time.
case Expired = 'expired'What the ceremony began with is gone: a code past its time, an attempt older than its lifetime.
case Throttled = 'throttled'Too many tries: the throttle refused before anything was checked.
case AccountRefused = 'account-refused'The account exists and the proof is good, but it may not sign in now (Result::$reason: banned, locked...).
case Refused = 'refused'A listener of Verifying or Beginning refused (omnishield: not a human); Result::$reason says its word.
case NoAttempt = 'no-attempt'A second factor with no first one passed before it in this attempt.
case NotOffered = 'not-offered'A second factor the attempt did not offer.
case NotEnrolled = 'not-enrolled'A factor this account has not set up.
Message class
final readonly class Message Message.php:10
What a method asks the host to send to an account: a sign-in link, a code.
The host's Sender renders it (omnibase: its Notifier and the "@Base/security/email/*" templates).
public Factor $factorpublic string $secretLink: the link's token; email-code: the code. Never logged, never stored as is.
public \DateTimeImmutable $until
Message::__construct()
public function __construct(public Factor $factor, public string $secret, public \DateTimeImmutable $until)Omniauth class
final class Omniauth Omniauth.php:39
The conductor: who signs in, by which factors, and what remains to pass.
Whatever the method, verify() does the same sequence - throttle, Verifying, the method, Accounts::check(), the second-factor rule (an ambient factor tried on the way), SignedIn or SignInFailed, the attempt kept or cleared - and that single sequence is what makes the behaviour the same under every host. The host keeps the request, the response, its session cookie (renewed at each sign-in), its remember-me and its pages; it checks the form's CSRF token (Omnibridge\Contract\Tokens) before calling.
const ATTEMPT = 'omniauth.attempt'Where Contract\State keeps the sign-in attempt.
const ENROLMENT = 'omniauth.enrolment.'Where it keeps an enrolment, per factor.
const LIFETIME = 600An attempt older than this (seconds) is gone.
Omniauth::__construct()
public function __construct(readonly Accounts $accounts, readonly Methods $methods, readonly State $state, ?SecondFactorPolicy $policy = null, readonly ?Throttle $throttle = null, readonly ?EventDispatcherInterface $events = null, ?ClockInterface $clock = null, readonly int $lifetime = self::LIFETIME)Omniauth::methods()
public function methods(): MethodsOmniauth::attempt()
public function attempt(): ?AttemptThe sign-in attempt in progress (the second-factor page reads Attempt::offered()), null when none.
Omniauth::abandon()
public function abandon(): voidForgets the attempt in progress: abandoned, or signed out.
Omniauth::begin()
public function begin(Factor|string $factor, Context $context, Proof|array $proof = []): ChallengeBegins a ceremony: a link sent for an address, a code sent for the account the first factor found, options to sign. What the method keeps goes into the attempt.
Omniauth::verify()
public function verify(Factor|string $factor, Context $context, Proof|array $proof = []): ResultVerifies a proof: a first factor starts a new attempt, a second one goes on with the attempt in progress.
Omniauth::enrol()
public function enrol(Factor|string $factor, object $account): EnrolmentSets a factor up for an account, from its settings: begin(), then confirm().
Omniauth::revoke()
public function revoke(Factor|string $factor, object $account): EnrolledTurns a factor off for an account (TrustedDevice: forgets every browser). A listener of Revoking may refuse; turning off any other factor makes the trusted devices fall back too.
PendingIdentity class
final readonly class PendingIdentity PendingIdentity.php:9
Someone a provider vouched for whom no account is linked to: what a sign-up form fills in, and what links the account once it exists.
public string $providerpublic string $subjectThe person's stable identifier at the provider ("sub").
public ?string $emailpublic bool $emailVerifiedWhether the provider says the address is verified (Google's email_verified).
public ?string $name
PendingIdentity::__construct()
public function __construct(public string $provider, public string $subject, public ?string $email = null, public bool $emailVerified = false, public ?string $name = null)Proof class
final readonly class Proof Proof.php:12
What the visitor sent: ['identifier' => ..., 'password' => ...], ['code' => ...], ['token' => ...], a WebAuthn assertion's JSON under 'json', an OAuth return's 'code' and 'state'. Two keys mean the same to every method: 'remember' (the host sets its remember-me) and 'trust' (a trusted-device cookie after a second factor).
Proof::__construct()
public function __construct(array $values = [])Proof::of()
public static function of(self|array $proof): selfProof::string()
public function string(string $key): stringThe value as a string; '' when it is missing or is not a scalar.
Proof::bool()
public function bool(string $key): boolProof::has()
public function has(string $key): boolProof::json()
public function json(): stringThe JSON a browser posted (a WebAuthn assertion): the 'json' key, or every value encoded.
Proof::all()
public function all(): arrayResult class
final readonly class Result Result.php:10
What a verification ended with: who, by which factors, what remains to pass, and what the host does next (open its session, set its remember-me, set the trusted-device cookie).
public Status $statuspublic ?object $accountpublic array $passedThe factors passed in this attempt, in their order.
public array $offeredSecondFactorRequired: what the second-factor page offers (any one suffices).
public ?Failure $failurepublic ?string $reasonAccountRefused: the account's reason (banned, locked...); Refused: the listener's.
public bool $rememberThe host sets its own remember-me.
public ?string $trustDeviceThe trusted_device cookie's new value, to set as is (scheb/2fa's format).
public ?PendingIdentity $identityIdentityUnknown: what a sign-up form fills in.
Result::__construct()
public function __construct(public Status $status, public ?object $account = null, public array $passed = [], public array $offered = [], public ?Failure $failure = null, public ?string $reason = null, public bool $remember = false, public ?string $trustDevice = null, public ?PendingIdentity $identity = null)Result::isAuthenticated()
public function isAuthenticated(): boolResult::needsSecondFactor()
public function needsSecondFactor(): boolResult::failed()
public static function failed(Failure $failure, ?string $reason = null, ?object $account = null): selfRole enum
enum Role Role.php:6
A first factor finds the account; a second one is asked of an account already found.
case First = 'first'case Second = 'second'
Status enum
enum Status Status.php:6
Where an attempt stands after a verification.
case Authenticated = 'authenticated'Signed in: the host opens its session.
case SecondFactorRequired = 'second-factor-required'A first factor passed; one of Result::$offered must follow.
case IdentityUnknown = 'identity-unknown'A provider vouched for someone no account is linked to (Result::$identity: what a sign-up form fills in).
case Failed = 'failed'Nothing passed: Result::$failure says why.
SystemClock class
final class SystemClock implements ClockInterface SystemClock.php:8
The machine's clock, when the host gives none (Testing\FixedClock in tests).
SystemClock::now()
public function now(): \DateTimeImmutableOmniauth\BackupCode
BackupCodeMethod class
final class BackupCodeMethod implements SecondFactor, Enrolling, Revocable BackupCodeMethod.php:28
Backup codes: handed out once (8 by default, "A1B2C3-D4E5F6" as omnibase draws them), kept as password_hash() hashes - the very hashes omnibase's user.backupCodes holds - and each good once: the one that serves is removed. They stand in for another second factor; alone, they ask for none (EnrolledSecondFactors).
verify(): ['code' => ...]; enrolConfirm() draws a new set, replaces the old one and returns the codes, to show once.
BackupCodeMethod::__construct()
public function __construct(readonly BackupCodes $codes, readonly int $count = 8)BackupCodeMethod::factor()
public function factor(): FactorBackupCodeMethod::enrolled()
public function enrolled(object $account): boolBackupCodeMethod::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictBackupCodeMethod::enrolBegin()
public function enrolBegin(Attempt $enrolment, Proof $proof, Context $context): ChallengeBackupCodeMethod::enrolConfirm()
public function enrolConfirm(Attempt $enrolment, Proof $proof, Context $context): EnrolledBackupCodeMethod::revoke()
public function revoke(object $account): voidBackupCodeMethod::draw()
public static function draw(int $count): arrayOmniauth\Contract
Accounts interface
interface Accounts Accounts.php:11
The application's accounts: opaque objects to omniauth, which implement nothing of it (omnibase fills this in on its UserRepository).
Accounts::byIdentifier()
public function byIdentifier(string $identifier): ?objectBy what the person types: an address, a user name.
Accounts::byHandle()
public function byHandle(string $handle): ?objectBy the stable handle (the id; it is also the passkeys' userHandle).
Accounts::handle()
public function handle(object $account): stringAccounts::identifier()
public function identifier(object $account): stringWhat the account is known by to its host: Symfony's getUserIdentifier().
The trusted-device tokens carry it (scheb/2fa's "usr" claim).
Accounts::displayName()
public function displayName(object $account): stringAccounts::email()
public function email(object $account): ?stringAccounts::check()
public function check(object $account): voidMay the account sign in now? Throws AccountRefused (banned, locked, restricted, disabled...) with a stable reason the page translates.
- Throws
AccountRefused
BackupCodes interface
interface BackupCodes BackupCodes.php:6
The backup codes' hashes, password_hash()'s (omnibase: user.backupCodes, a JSON list).
BackupCodes::hashes()
public function hashes(object $account): arrayBackupCodes::replace()
public function replace(object $account, array $hashes): voidEmailCodes interface
interface EmailCodes EmailCodes.php:10
The code sent by e-mail (omnibase: user.emailAuthEnabled, user.emailAuthCode). The store keeps the code's keyed hash, never the code; its deadline is kept in the attempt.
EmailCodes::enabled()
public function enabled(object $account): boolEmailCodes::enable()
public function enable(object $account): voidEmailCodes::disable()
public function disable(object $account): voidEmailCodes::remember()
public function remember(object $account, string $hash): voidEmailCodes::remembered()
public function remembered(object $account): ?stringEmailCodes::forget()
public function forget(object $account): voidIdentities interface
interface Identities Identities.php:11
The accounts linked to an identity provider, by the person's identifier there ("sub"); read by Omniauth\OAuth\OAuthMethod (omnibase: the userIdentity table).
One subject of a provider has one account; an account has one identity per provider.
Identities::owner()
public function owner(string $provider, string $subject): ?objectIdentities::link()
public function link(object $account, string $provider, string $subject, ?string $email = null, bool $emailVerified = false): voidLinks the account to that identity, with what the provider said of the address at that moment ($email, and whether it vouched for it).
Identities::unlink()
public function unlink(object $account, string $provider): voidUnlinks the account from that provider (the settings' "disconnect"); nothing when it was not linked.
Identities::used()
public function used(string $provider, string $subject, \DateTimeImmutable $at): voidThe provider vouched for that identity again (a sign-in by it): its last-used date.
Identities::providers()
public function providers(object $account): array- Returns
the providers the account is linked to
LinkTokens interface
interface LinkTokens LinkTokens.php:9
The sign-in links in flight. The store sees their SHA-256, never their value: whoever reads the table cannot sign in with it.
LinkTokens::issue()
public function issue(object $account, string $hash, \DateTimeImmutable $until): voidKeeps a link issued to the account until that moment (beside the others, or in their place: the store's choice).
LinkTokens::consume()
public function consume(string $hash, \DateTimeImmutable $now): ?objectThe account the link of that hash was issued to, when it is still valid at $now; the link is used up on the way: it serves once.
Passwords interface
interface Passwords Passwords.php:6
The accounts' password hashes (omnibase: user.password).
Passwords::hash()
public function hash(object $account): ?stringThe hash as the hasher wrote it, or null when the account has no password.
Passwords::update()
public function update(object $account, string $hash): voidKeeps a new hash: a rehash at sign-in, or a new password.
PlainLinkTokens interface
interface PlainLinkTokens PlainLinkTokens.php:11
For the switch only: the links issued before omniauth were kept in clear (omnibase's userToken "login-token"). A LinkTokens store that still holds some also implements this, and they are read as they are until they expire; once none is left, the store stops implementing it.
PlainLinkTokens::consumePlain()
public function consumePlain(string $value, \DateTimeImmutable $now): ?objectSecondFactorPolicy interface
interface SecondFactorPolicy SecondFactorPolicy.php:9
The second-factor rule: what an account must still pass.
SecondFactorPolicy::required()
public function required(object $account, array $passed, Context $context): arrayThe second factors this account must still pass after $passed (none: it is done).
- Returns
those it may present (any one suffices)
Sender interface
interface Sender Sender.php:8
Sends a sign-in link or a code to an account (omnibase: its Notifier; tests: Testing\RecordingSender).
Sender::send()
public function send(object $account, Message $message): voidState interface
interface State State.php:10
The visitor's own state between two requests: the session under Symfony and Laravel, Omniauth\ArrayState in plain PHP and tests. Values are arrays of scalars.
State::get()
public function get(string $key): mixedState::set()
public function set(string $key, mixed $value): voidState::remove()
public function remove(string $key): voidThrottle interface
interface Throttle Throttle.php:12
Limits the tries: Symfony's RateLimiterFactory, Laravel's RateLimiter, Omniauth\CacheThrottle on any PSR-6 or PSR-16 cache.
A scope names what is limited ("password", "email-code", "link.begin"...), a key whom (an identifier and an address, an account).
Throttle::consume()
public function consume(string $scope, string $key): boolCounts one try; false when the limit is reached (nothing is checked then).
Throttle::reset()
public function reset(string $scope, string $key): voidForgets the tries: after a success.
TotpSecrets interface
interface TotpSecrets TotpSecrets.php:6
The authenticator applications' secrets, base32 (omnibase: user.secret); read by omniauth/otp (SealedTotpSecrets seals them at rest).
TotpSecrets::secret()
public function secret(object $account): ?stringTotpSecrets::enable()
public function enable(object $account, string $secret): voidTotpSecrets::disable()
public function disable(object $account): voidTrustedVersion interface
interface TrustedVersion TrustedVersion.php:9
The trusted devices' version (omnibase: user.trustedTokenVersion, scheb's TrustedDeviceInterface): a token of another version no longer counts.
TrustedVersion::version()
public function version(object $account): intTrustedVersion::revokeAll()
public function revokeAll(object $account): voidRaises the version: every device trusted so far falls back to the second factor.
Omniauth\EmailCode
EmailCodeMethod class
final class EmailCodeMethod implements Challenging, SecondFactor, Enrolling, Revocable EmailCodeMethod.php:35
A code sent by e-mail once a first factor passed: 6 digits (as scheb/2fa draws them), good for 10 minutes and once. By Marco's decision QA5 (2026-10-20) the store keeps its hash, never the code: an HMAC-SHA256 keyed by the host's secret and bound to the account, compared in constant time; the deadline is kept in the attempt.
begin() sends it (the second-factor page calls it, or a "send again" button); verify(): ['code' => ...]. Enrolment: enrolBegin() sends a code, enrolConfirm() checks it and turns the factor on.
const LIFETIME = 600
EmailCodeMethod::__construct()
public function __construct(readonly Accounts $accounts, readonly EmailCodes $codes, readonly Sender $sender, readonly string $secret, readonly int $digits = 6, readonly int $lifetime = self::LIFETIME, ?ClockInterface $clock = null)EmailCodeMethod::factor()
public function factor(): FactorEmailCodeMethod::enrolled()
public function enrolled(object $account): boolEmailCodeMethod::begin()
public function begin(Attempt $attempt, Proof $proof, Context $context): ChallengeEmailCodeMethod::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictEmailCodeMethod::enrolBegin()
public function enrolBegin(Attempt $enrolment, Proof $proof, Context $context): ChallengeEmailCodeMethod::enrolConfirm()
public function enrolConfirm(Attempt $enrolment, Proof $proof, Context $context): EnrolledEmailCodeMethod::revoke()
public function revoke(object $account): voidEmailCodeMethod::hash()
public function hash(object $account, string $code): stringWhat the store keeps of a code.
Omniauth\Event
AccountRefused class
final class AccountRefused AccountRefused.php:9
The proof was good but the account may not sign in (omnibase: the "welcome back" e-mail of a disabled account).
public readonly object $accountpublic readonly string $reasonpublic readonly Factor $factorpublic readonly Context $context
AccountRefused::__construct()
public function __construct(public readonly object $account, public readonly string $reason, public readonly Factor $factor, public readonly Context $context)Beginning class
final class Beginning Beginning.php:10
Before a ceremony begins (a link or a code is about to be sent): a listener may refuse.
public readonly Factor $factorpublic readonly Context $contextpublic readonly Attempt $attemptpublic readonly ?string $identifier
Beginning::__construct()
public function __construct(public readonly Factor $factor, public readonly Context $context, public readonly Attempt $attempt, public readonly ?string $identifier = null)Beginning::refuse()
public function refuse(string $reason): voidBeginning::refusal()
public function refusal(): ?stringCredentialAdded class
final class CredentialAdded CredentialAdded.php:8
A factor was set up, or a password changed.
public readonly object $accountpublic readonly Factor $factor
CredentialAdded::__construct()
public function __construct(public readonly object $account, public readonly Factor $factor)CredentialRemoved class
final class CredentialRemoved CredentialRemoved.php:8
A factor was turned off; the trusted devices fell back with it.
public readonly object $accountpublic readonly Factor $factor
CredentialRemoved::__construct()
public function __construct(public readonly object $account, public readonly Factor $factor)Revoking class
final class Revoking Revoking.php:12
Before a factor is turned off: omnibase's rule ("the administrator wins": a role that requires a second factor keeps it) refuses here, so a hidden button and a forged POST meet the same predicate.
public readonly object $accountpublic readonly Factor $factor
Revoking::__construct()
public function __construct(public readonly object $account, public readonly Factor $factor)Revoking::refuse()
public function refuse(string $reason): voidRevoking::refusal()
public function refusal(): ?stringSecondFactorRequired class
final class SecondFactorRequired SecondFactorRequired.php:9
A first factor passed and a second one is due.
public readonly object $accountpublic readonly array $passedpublic readonly array $offeredpublic readonly Context $context
SecondFactorRequired::__construct()
public function __construct(public readonly object $account, public readonly array $passed, public readonly array $offered, public readonly Context $context)SignInFailed class
final class SignInFailed SignInFailed.php:10
A verification failed: logged with the address and the factor, never with the secret; SignInGuard counts it.
public readonly Factor $factorpublic readonly Failure $failurepublic readonly Context $contextpublic readonly ?string $identifierpublic readonly ?object $accountpublic readonly ?string $reason
SignInFailed::__construct()
public function __construct(public readonly Factor $factor, public readonly Failure $failure, public readonly Context $context, public readonly ?string $identifier = null, public readonly ?object $account = null, public readonly ?string $reason = null)SignedIn class
final class SignedIn SignedIn.php:9
Signed in: omnibase's UserTracker, the "new device" e-mail, the account brought up to date listen here.
public readonly object $accountpublic readonly array $passedpublic readonly Context $context
SignedIn::__construct()
public function __construct(public readonly object $account, public readonly array $passed, public readonly Context $context)Verifying class
final class Verifying Verifying.php:14
Before a method checks a proof (and after the throttle let it through): a listener may refuse - omnishield when the visitor is not a human, omnibase's SignInGuard when a captcha is due.
public readonly Factor $factorpublic readonly Context $contextpublic readonly Attempt $attemptpublic readonly ?string $identifierWhat the visitor typed to be found (first factors), or null.
Verifying::__construct()
public function __construct(public readonly Factor $factor, public readonly Context $context, public readonly Attempt $attempt, public readonly ?string $identifier = null)Verifying::refuse()
public function refuse(string $reason): voidVerifying::refusal()
public function refusal(): ?stringOmniauth\Exception
AccountRefused class
final class AccountRefused extends RuntimeException implements OmniauthException AccountRefused.php:6
Thrown by Accounts::check(): the account may not sign in now, for a stable reason the page translates.
public readonly string $reason
AccountRefused::__construct()
public function __construct(public readonly string $reason, ?\Throwable $previous = null)OmniauthException interface
interface OmniauthException extends Throwable OmniauthException.php:6
Every exception omniauth throws.
UnknownMethod class
final class UnknownMethod extends InvalidArgumentException implements OmniauthException UnknownMethod.php:8
A factor no method was mounted for: a host's mistake, not a visitor's.
UnknownMethod::for()
public static function for(Factor|string $factor, array $mounted): selfOmniauth\Link
LinkMethod class
final class LinkMethod implements Challenging LinkMethod.php:32
A sign-in link sent to the account's address, by Marco's decision QA4 (2026-10-20): good for 15 minutes, once, and kept hashed (SHA-256) - the store never sees its value. Each request sends a new link (the conductor limits them: "link.begin", 3 an hour by default), and the answer is the same whether the address has an account or not.
begin(): ['identifier' => the address]; verify(): ['token' => the link's token]. The link's page signs in a GET from a mailbox; the host's remember-me follows (as omnibase's link did).
const LIFETIME = 900
LinkMethod::__construct()
public function __construct(readonly Accounts $accounts, readonly LinkTokens $tokens, readonly Sender $sender, readonly int $lifetime = self::LIFETIME, ?ClockInterface $clock = null)LinkMethod::factor()
public function factor(): FactorLinkMethod::begin()
public function begin(Attempt $attempt, Proof $proof, Context $context): ChallengeLinkMethod::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictLinkMethod::hash()
public static function hash(string $value): stringWhat the store keeps of a link's token.
Omniauth\Method
Ambient interface
interface Ambient extends Method Ambient.php:13
A second factor the visitor does not present: it is read from the context (the trusted-device cookie). The conductor tries it by itself when a second factor is required, and asks it what to leave on the browser when the visitor asked to be trusted after passing another one.
Ambient::remember()
public function remember(object $account, Context $context): stringWhat the host keeps on the browser (a cookie's value) so that this factor passes next time.
Challenge class
final readonly class Challenge Challenge.php:8
What the host gives back after a beginning: JSON for a script, an address to go to, "it was sent", or why not.
const NONE = 'none'const JSON = 'json'const REDIRECT = 'redirect'const SENT = 'sent'const FAILED = 'failed'
Challenge::none()
public static function none(array $data = []): selfNothing to give: the visitor goes on to the next step.
Challenge::json()
public static function json(string $json): selfOptions for a script (WebAuthn's), as they are.
Challenge::redirect()
public static function redirect(string $url): selfChallenge::sent()
public static function sent(array $data = []): selfA link or a code went out - or the address has no account: the same answer for both.
Challenge::failed()
public static function failed(Failure $failure, ?string $reason = null): selfChallenge::hasFailed()
public function hasFailed(): boolChallenging interface
interface Challenging extends Method Challenging.php:14
A method that first gives something to the browser or the mailbox: options to sign, an address to go to, a link or a code sent. What it must remember goes into the attempt (Attempt::keep()), never into the challenge.
Challenging::begin()
public function begin(Attempt $attempt, Proof $proof, Context $context): Challenge$proofwhat the visitor sent to begin with (the link's address), often nothing
Enrolling interface
interface Enrolling extends Method Enrolling.php:15
A method an account sets up from its settings, in the same two steps as a sign-in. The attempt is the enrolment's own (its account identified), kept apart from any sign-in.
Enrolling::enrolBegin()
public function enrolBegin(Attempt $enrolment, Proof $proof, Context $context): ChallengeWhat the visitor needs to set it up: a code sent, a secret and its otpauth:// address, options to sign, or nothing.
Enrolling::enrolConfirm()
public function enrolConfirm(Attempt $enrolment, Proof $proof, Context $context): EnrolledChecks what the visitor sent back and keeps the credential; the secrets to show once, if any.
Method interface
interface Method Method.php:11
A way to sign in: it checks a proof within an attempt, and touches neither request nor response.
Method::factor()
public function factor(): FactorMethod::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictMethods class
final class Methods implements IteratorAggregate, Countable Methods.php:9
The methods a host mounted, one per factor.
Methods::__construct()
public function __construct(iterable $methods = [])Methods::add()
public function add(Method $method): voidMethods::has()
public function has(Factor|string $factor): boolMethods::get()
public function get(Factor|string $factor): Method- Throws
UnknownMethod
Methods::secondFactors()
public function secondFactors(): array- Returns
the second factors an account sets up (not the ambient ones)
Methods::ambient()
public function ambient(): arrayMethods::getIterator()
public function getIterator(): \ArrayIteratorMethods::count()
public function count(): intRevocable interface
interface Revocable extends Method Revocable.php:6
A factor an account can turn off (or, for trusted devices, forget every browser).
Revocable::revoke()
public function revoke(object $account): voidSecondFactor interface
interface SecondFactor extends Method SecondFactor.php:6
A second factor an account sets up: the policy asks it whether this one has.
SecondFactor::enrolled()
public function enrolled(object $account): boolVerdict class
final readonly class Verdict Verdict.php:9
What a method concluded of a proof.
Verdict::passed()
public static function passed(object $account, bool $remember = false): self$rememberthe method itself asks the host for its remember-me (a link from a mailbox)
Verdict::refused()
public static function refused(Failure $failure, ?string $reason = null): selfVerdict::expired()
public static function expired(): selfVerdict::unknown()
public static function unknown(PendingIdentity $identity): selfA provider vouched for someone no account is linked to.
Verdict::hasPassed()
public function hasPassed(): boolOmniauth\OAuth
Authorization class
final readonly class Authorization Authorization.php:18
What one trip to a provider is bound to, drawn at the beginning and kept in the attempt (never in the answer): the state the return must bring back (against a forged return: login CSRF), the PKCE verifier whose S256 challenge goes in the authorization address (against a code stolen or injected: only the one who began can exchange it, RFC 7636), and the nonce an ID token must carry (OpenID Connect, against a token replayed) - and the origin of the request that began the trip, against which a provider configured with a return path ("/login/google/callback") makes its return address: the host the visitor is on at that request, not the first one a long-running worker served, and the same address again at the exchange (RFC 6749 §4.1.3: the token request repeats the authorization's).
43 characters of base64url: RFC 7636 §4.1 asks 43 to 128.
The request's scheme and host at the beginning ("https://example.org:8443"), null when it is not known.
Authorization::__construct()
public function __construct(public string $state, public string $verifier, public string $nonce, public ?string $origin = null)Authorization::fresh()
public static function fresh(?string $origin = null): selfThree new values, 32 random bytes each; the beginning request's origin when the host knows it.
Authorization::returnAddress()
public function returnAddress(string $configured): stringA provider's return address for this trip: an absolute address as it is; a path ("/login/google/callback") on the trip's origin.
Authorization::challenge()
public function challenge(): stringThe verifier's S256 challenge: BASE64URL(SHA-256(verifier)), RFC 7636 §4.2.
Authorization::matches()
public function matches(string $state): boolWhether a return brings back this state: compared in constant time.
Authorization::toArray()
public function toArray(): array- Returns
what the attempt keeps
Authorization::fromArray()
public static function fromArray(mixed $kept): ?selfWhat the attempt gave back; null when anything is missing.
Authorization::base64url()
public static function base64url(string $bytes): stringIdentity class
final readonly class Identity Identity.php:11
Whom a provider vouched for, read from its ID token or its userinfo - never from the return address's parameters.
public string $providerpublic string $subjectThe person's stable identifier at the provider ("sub"): what links an account.
public ?string $emailpublic bool $emailVerifiedWhether the provider says the address is the person's (Google's email_verified); false when it says nothing.
public ?string $namepublic array $claimswhat the provider said, as it said it
Identity::__construct()
public function __construct(public string $provider, public string $subject, public ?string $email = null, public bool $emailVerified = false, public ?string $name = null, public array $claims = [])$claimswhat the provider said, as it said it
Identity::pending()
public function pending(): PendingIdentityWhat the conductor's IdentityUnknown result carries to the host's sign-up.
Identity::verified()
public static function verified(mixed $claim): boolA provider's "true": a JSON boolean, or the string some of them send.
NoSecondFactorLinking class
final readonly class NoSecondFactorLinking implements EmailLinking NoSecondFactorLinking.php:15
QA3's rule for the address: an account that set up a second factor (an authenticator application, the code by e-mail) is not linked by its address - whoever holds the provider's account would then be one factor away from it for good. It reads the methods mounted, so it can be built before the OAuth method is added to them.
NoSecondFactorLinking::__construct()
public function __construct(Methods $methods)NoSecondFactorLinking::allows()
public function allows(object $account, Identity $identity): boolOAuthMethod class
final class OAuthMethod implements Challenging, Enrolling OAuthMethod.php:67
Signing in through an identity provider (OAuth 2, OpenID Connect), a first factor: begin() draws a state, a PKCE verifier and a nonce, keeps them in the attempt and answers with the provider's authorization address (Challenge::redirect()); verify() takes the return's ['code' => …, 'state' => …] (or its ['error' => …]), takes what the attempt kept - once: a return comes back once -, compares the state in constant time, has the provider exchange the code with the verifier and read the person (an ID token or a userinfo, never the address's parameters), then finds the account (Marco's decision QA3):
1. the account linked to that provider's subject ("sub"), whatever its
address says now;
2. else, only when the provider says the address is verified, the account
of that address - linked there and then, unless $byEmail refuses it
(false: never; an EmailLinking: NoSecondFactorLinking, omnibase's rule),
and never an account already linked to another subject of that
provider: then not-enrolled (reason link-from-settings): the person
signs in otherwise and links the provider from the settings;
3. else nobody: the conductor answers Status::IdentityUnknown with the
PendingIdentity a sign-up form fills in - no account is ever created
here (lesringostars' transient account and its 500 "User identifier is
NULL" are gone); once the host has made the account, adopt() links it.
Then the conductor does what it does after any first factor: the account checked, and the second factor asked when the account has one (QA2: EnrolledSecondFactors::AFTER holds OAuth).
From the settings, enrolBegin()/enrolConfirm() link a provider to the account signed in (['provider' => 'google']): the same trip, the subject refused when another account holds it; Identities::unlink() undoes it. A link keeps the address the provider gave then and whether it vouched for it; each sign-in by an identity marks it used (Identities::used()).
Refusals are bad-credentials with a stable reason: unknown-provider,
state-mismatch, provider-denied (the person said no), provider-error,
missing-code, exchange-failed (a code refused - a PKCE verifier that is
not the code's among the causes), invalid-id-token, no-subject,
hosted-domain, linked-elsewhere, other-identity; and expired when
the attempt kept nothing (no beginning, a return replayed, ten minutes
gone).
OAuthMethod::__construct()
public function __construct(readonly Accounts $accounts, readonly Identities $identities, readonly Providers $providers, readonly EmailLinking|bool $byEmail = true, ?ClockInterface $clock = null)OAuthMethod::factor()
public function factor(): FactorOAuthMethod::providers()
public function providers(): ProvidersOAuthMethod::begin()
public function begin(Attempt $attempt, Proof $proof, Context $context): ChallengeOAuthMethod::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictOAuthMethod::enrolBegin()
public function enrolBegin(Attempt $enrolment, Proof $proof, Context $context): ChallengeOAuthMethod::enrolConfirm()
public function enrolConfirm(Attempt $enrolment, Proof $proof, Context $context): EnrolledOAuthMethod::adopt()
public function adopt(object $account, PendingIdentity $identity): EnrolledAfter a sign-up from Status::IdentityUnknown: links the new account to the identity the result carried - as the host kept it on its side (its session), never as a form posted it back.
Omniauth\OAuth\Contract
EmailLinking interface
interface EmailLinking EmailLinking.php:15
May an account be linked to an identity by its address alone - the provider having said the address is verified, and no account linked to that identity yet? Marco's decision QA3: never for an account that has a second factor or an administration role (NoSecondFactorLinking says the first; omnibase's rule, step AU4, adds its roles); the person then signs in otherwise and links the provider from the account's settings.
EmailLinking::allows()
public function allows(object $account, Identity $identity): boolOmniauth\OAuth\Exception
ProviderError class
final class ProviderError extends RuntimeException implements OmniauthException ProviderError.php:14
The provider did not vouch for anyone: the code refused at the exchange, an ID token not for this client, past its time or without the nonce, a profile without its subject. $reason is the stable word the method answers with; the message (and the previous exception: league's, Guzzle's) is for the server's log, not the page.
const EXCHANGE = 'exchange-failed'const ID_TOKEN = 'invalid-id-token'const SUBJECT = 'no-subject'const HOSTED_DOMAIN = 'hosted-domain'public readonly string $reason
ProviderError::__construct()
public function __construct(public readonly string $reason, string $message = '', ?\Throwable $previous = null)Omniauth\OAuth\Provider
LeagueProvider class
class LeagueProvider implements Provider LeagueProvider.php:34
A league/oauth2-client provider as omniauth's Provider - the ground of the provider packages built on league (omniauth/google's GoogleProvider), and any other league provider by hand (league/oauth2-facebook, a GenericProvider on a provider's three addresses...) until it has a package of its own. league/oauth2-client is not required by omniauth: the provider packages require it (this class loads without it, and is only usable with it). The authorization address with the state and the S256 challenge omniauth drew, the code exchanged with its verifier, the person read from the resource owner's details through the access token. $fields names the details' keys: the subject (its resource owner's getId() when null), the address, whether it is verified (null: the provider says nothing, so it is not - no account is ever linked by such an address), the name.
PKCE is omniauth's, not league's: leave the league provider's "pkceMethod" unset (it would draw a verifier of its own that no attempt keeps).
$redirectUri, when given, is the return address of each trip: an absolute address, or a path the origin of the request that began the trip completes (Authorization::returnAddress()) - sent in the authorization address and again at the exchange, whatever the league provider was built with.
const FIELDS = ['subject' => null, 'email' => 'email', 'verified' => null, 'name' => 'name']
LeagueProvider::__construct()
public function __construct(protected readonly string $name, protected readonly AbstractProvider $league, protected readonly array $scopes = [], array $fields = [], protected readonly ?string $redirectUri = null)LeagueProvider::name()
public function name(): stringLeagueProvider::league()
public function league(): AbstractProviderThe league provider underneath (its HTTP client, its options).
LeagueProvider::authorizationUrl()
public function authorizationUrl(Authorization $authorization): stringLeagueProvider::identify()
public function identify(string $code, Authorization $authorization): IdentityProvider interface
interface Provider Provider.php:14
An identity provider: where to send the visitor, then whom the code that came back vouches for. Its return address is its own, fixed at its configuration (never taken from a request).
Provider::name()
public function name(): stringIts key: the {provider} of the host's routes, Identities' $provider ("google").
Provider::authorizationUrl()
public function authorizationUrl(Authorization $authorization): stringThe address to send the visitor to, carrying the state, the PKCE challenge and the nonce.
Provider::identify()
public function identify(string $code, Authorization $authorization): IdentityExchanges the code (with the PKCE verifier) and reads whom it vouches for.
- Throws
ProviderError
ProviderFactory interface
interface ProviderFactory ProviderFactory.php:10
Makes the providers of one kind from a host's configuration: each provider package has its own (omniauth/google's GoogleFactory), so this package needs no OAuth library. Providers::configured() asks each factory in turn.
ProviderFactory::create()
public function create(string $name, array $entry): ?ProviderThe provider of this entry, or null when the entry is not of this factory's kind.
$entrythe entry as configured: client_id, client_secret, redirect_uri (an absolute address, or a path the trip's origin completes), scopes, type, and the kind's own keys
Providers class
final class Providers implements IteratorAggregate, Countable Providers.php:12
The providers a host serves, by name. Marco's decisions: Google alone today (QA7), and one package per provider (2026-10-21) - omniauth/google first; configured() makes them through each package's factory.
Providers::__construct()
public function __construct(Provider ...$providers)Providers::configured()
public static function configured(array $config, ProviderFactory ...$factories): selfFrom a host's configuration (omniauth.yaml's "oauth" tree, config/omniauth.php's):
google: { client_id: …, client_secret: …, redirect_uri: /login/google/callback, scopes: [openid, email, profile] }
Each entry is made by the first factory that knows its kind (omniauth/google's GoogleFactory for "google": one package per provider). An entry with no client_id is left out: a provider without credentials is not served. Its redirect_uri is an absolute address, or a path the origin of each trip's request completes (Authorization::returnAddress()): under a long-running worker the visitor returns to the host they left from.
Providers::fromConfig()
public static function fromConfig(array $config, ?ClockInterface $clock = null, array $collaborators = []): selfuse Providers::configured() with omniauth/google's GoogleFactory: Google moved to its own package (this one requires no OAuth library any more). Kept for one release; it serves Google entries alone.
$collaboratorsleague's, for every provider (an "httpClient")
Providers::has()
public function has(string $name): boolProviders::get()
public function get(string $name): ?ProviderProviders::names()
public function names(): array- Returns
what the sign-in page offers
Providers::getIterator()
public function getIterator(): \ArrayIteratorProviders::count()
public function count(): intOmniauth\OAuth\Testing
FakeProvider class
final class FakeProvider implements Provider FakeProvider.php:26
An authorization server in memory, for a host's tests and a first try: it answers the authorization address as a provider does once the person consented (consent(): the return's code and state), and exchanges a code as a provider does - once, for the client and the return address it was given to, and only with the PKCE verifier whose S256 challenge the authorization carried (RFC 7636 §4.6); the nonce is the one the address carried. So a test signs in with no network, and a forged or injected return fails here as it would at Google.
Its return address may be a path ("/login/google/callback"): each trip's address is then that path on the origin the trip began at (Authorization::returnAddress()), and a code is exchanged only for the address it was given to - as Google compares the token request's redirect_uri with the authorization's.
public array $exchanges = []public readonly string $redirectUripublic readonly string $clientId
FakeProvider::__construct()
public function __construct(readonly string $name = 'google', public readonly string $redirectUri = 'https://example.org/login/google/callback', public readonly string $clientId = 'omniauth-test-client', readonly array $scopes = ['openid', 'email', 'profile'])FakeProvider::name()
public function name(): stringFakeProvider::authorizationUrl()
public function authorizationUrl(Authorization $authorization): stringFakeProvider::consent()
public function consent(string $authorizationUrl, Identity|array $person): arrayThe person consents at the authorization address: what the browser brings back to the return address (['code' => …, 'state' => …]).
FakeProvider::deny()
public function deny(string $authorizationUrl): array- Returns
the person said no
FakeProvider::returnUrl()
public function returnUrl(array $return): stringThe return address the browser lands on with $return's parameters.
FakeProvider::identify()
public function identify(string $code, Authorization $authorization): IdentityOmniauth\Password
PasswordMethod class
final class PasswordMethod implements Enrolling PasswordMethod.php:31
The password, checked by symfony/password-hasher under every host - never by password_verify() alone: Symfony prehashes a password longer than 72 bytes (or holding a NUL byte) before bcrypt, so password_verify(), and Laravel's Hash with it, would refuse those accounts. The same hasher says when to rehash (cost 13 here, 12 by Laravel's default: without it, each sign-in on the other host would rehash).
Proof: ['identifier' => ..., 'password' => ...]; an enrolment (a new password): ['password' => ..., 'current' => ...], 'current' checked when given.
PasswordMethod::__construct()
public function __construct(readonly Accounts $accounts, readonly Passwords $passwords, ?PasswordHasherInterface $hasher = null)$hasherthe one security.yaml sets; by default bcrypt, cost 13, Symfony's "auto"
PasswordMethod::factor()
public function factor(): FactorPasswordMethod::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictPasswordMethod::enrolBegin()
public function enrolBegin(Attempt $enrolment, Proof $proof, Context $context): ChallengePasswordMethod::enrolConfirm()
public function enrolConfirm(Attempt $enrolment, Proof $proof, Context $context): EnrolledOmniauth\Testing
ArrayCache class
final class ArrayCache implements CacheInterface ArrayCache.php:12
A PSR-16 cache in a PHP array, its lifetimes read on the clock given: what CacheThrottle counts in for tests and a first try, with no cache library.
ArrayCache::__construct()
public function __construct(readonly ?ClockInterface $clock = null)ArrayCache::get()
public function get(string $key, mixed $default = null): mixedArrayCache::set()
public function set(string $key, mixed $value, \DateInterval|int|null $ttl = null): boolArrayCache::delete()
public function delete(string $key): boolArrayCache::clear()
public function clear(): boolArrayCache::getMultiple()
public function getMultiple(iterable $keys, mixed $default = null): iterableArrayCache::setMultiple()
public function setMultiple(iterable $values, \DateInterval|int|null $ttl = null): boolArrayCache::deleteMultiple()
public function deleteMultiple(iterable $keys): boolArrayCache::has()
public function has(string $key): boolFixedClock class
final class FixedClock implements ClockInterface FixedClock.php:8
A clock that stands still until told: deadlines and windows tested to the second.
FixedClock::__construct()
public function __construct(\DateTimeImmutable|string $now = '2026-10-20 12:00:00 UTC')FixedClock::now()
public function now(): \DateTimeImmutableFixedClock::set()
public function set(\DateTimeImmutable|string $now): voidFixedClock::advance()
public function advance(int $seconds): voidInMemoryEmailCodes class
final class InMemoryEmailCodes implements EmailCodes InMemoryEmailCodes.php:8
The e-mailed codes on TestAccount objects (InMemoryStore::emailCodes()).
InMemoryEmailCodes::enabled()
public function enabled(object $account): boolInMemoryEmailCodes::enable()
public function enable(object $account): voidInMemoryEmailCodes::disable()
public function disable(object $account): voidInMemoryEmailCodes::remember()
public function remember(object $account, string $hash): voidInMemoryEmailCodes::remembered()
public function remembered(object $account): ?stringInMemoryEmailCodes::forget()
public function forget(object $account): voidInMemoryStore class
final class InMemoryStore implements Accounts, Passwords, LinkTokens, PlainLinkTokens, BackupCodes, TrustedVersion, Identities InMemoryStore.php:21
Every store contract in memory, over TestAccount objects: tests, the harness's bare script, a first try in plain PHP. An application fills the same contracts on its tables (omnibase: Base\Auth\). The e-mailed codes and the TOTP secrets, whose contracts share method names, are emailCodes() and totpSecrets(), over the same accounts.
InMemoryStore::__construct()
public function __construct(TestAccount ...$accounts)InMemoryStore::add()
public function add(TestAccount $account): TestAccountInMemoryStore::emailCodes()
public function emailCodes(): InMemoryEmailCodesInMemoryStore::totpSecrets()
public function totpSecrets(): InMemoryTotpSecretsInMemoryStore::byIdentifier()
public function byIdentifier(string $identifier): ?objectInMemoryStore::byHandle()
public function byHandle(string $handle): ?objectInMemoryStore::handle()
public function handle(object $account): stringInMemoryStore::identifier()
public function identifier(object $account): stringInMemoryStore::displayName()
public function displayName(object $account): stringInMemoryStore::email()
public function email(object $account): ?stringInMemoryStore::check()
public function check(object $account): voidInMemoryStore::hash()
public function hash(object $account): ?stringInMemoryStore::update()
public function update(object $account, string $hash): voidInMemoryStore::issue()
public function issue(object $account, string $hash, \DateTimeImmutable $until): voidInMemoryStore::consume()
public function consume(string $hash, \DateTimeImmutable $now): ?objectInMemoryStore::consumePlain()
public function consumePlain(string $value, \DateTimeImmutable $now): ?objectInMemoryStore::hashes()
public function hashes(object $account): arrayInMemoryStore::replace()
public function replace(object $account, array $hashes): voidInMemoryStore::version()
public function version(object $account): intInMemoryStore::revokeAll()
public function revokeAll(object $account): voidInMemoryStore::owner()
public function owner(string $provider, string $subject): ?objectInMemoryStore::link()
public function link(object $account, string $provider, string $subject, ?string $email = null, bool $emailVerified = false): voidInMemoryStore::unlink()
public function unlink(object $account, string $provider): voidInMemoryStore::used()
public function used(string $provider, string $subject, \DateTimeImmutable $at): voidInMemoryStore::providers()
public function providers(object $account): arrayInMemoryTotpSecrets class
final class InMemoryTotpSecrets implements TotpSecrets InMemoryTotpSecrets.php:8
The TOTP secrets on TestAccount objects (InMemoryStore::totpSecrets()), for omniauth/otp's tests.
InMemoryTotpSecrets::secret()
public function secret(object $account): ?stringInMemoryTotpSecrets::enable()
public function enable(object $account, string $secret): voidInMemoryTotpSecrets::disable()
public function disable(object $account): voidProviderContractTests trait
trait ProviderContractTests ProviderContractTests.php:44
What every identity provider does, as PHPUnit tests: a provider package's test case uses this trait, gives the provider and a way to make its server answer (recorded answers: no test reaches the network), and the suite checks the Provider contract:
- the authorization address carries the trip's state, its S256 challenge
(never the verifier), the nonce,
response_type=code, the client and the return address - the configured one, or its path on the trip's origin (none known: a LogicException) - and asks the server nothing; - a code the server vouches for is exchanged for an Identity of this provider: its subject, an address said verified only when there is one;
- a code the server refuses, and a server that does not answer, are a ProviderError (reason "exchange-failed") - never another exception, and never with the client's secret in its message;
- made by its factory (when it has one) from a configuration entry: named after the entry's key, null for an entry of another kind; through Providers::configured(), an entry without a client_id is left out and one without a redirect_uri is a mistake.
final class GoogleContractTest extends TestCase { use ProviderContractTests;
protected function provider(string $redirectUri): Provider { ... a GoogleProvider on a Guzzle MockHandler ... }
protected function consented(Provider $provider, Authorization $authorization): string { ... the token answer queued, with the nonce ...; return 'a-code'; }
protected function refused(Provider $provider): string { ... an invalid_grant queued ...; return 'a-code'; }
protected function unanswered(Provider $provider): string { ... a 503 queued ...; return 'a-code'; }
}Requires phpunit/phpunit (the package's require-dev).
ProviderContractTests::testTheAuthorizationAddressCarriesTheTrip()
public function testTheAuthorizationAddressCarriesTheTrip(): voidProviderContractTests::testAReturnPathIsCompletedByTheTripsOrigin()
public function testAReturnPathIsCompletedByTheTripsOrigin(): voidProviderContractTests::testACodeTheServerVouchesForIsAnIdentityOfThisProvider()
public function testACodeTheServerVouchesForIsAnIdentityOfThisProvider(): voidProviderContractTests::testARefusedOrUnansweredExchangeIsAProviderError()
public function testARefusedOrUnansweredExchangeIsAProviderError(): voidProviderContractTests::testItsFactoryMakesItFromAConfigurationEntry()
public function testItsFactoryMakesItFromAConfigurationEntry(): voidRecordingSender class
final class RecordingSender implements Sender RecordingSender.php:9
Sends nothing, keeps every message: the link or the code a test reads back.
public array $sent = []
RecordingSender::send()
public function send(object $account, Message $message): voidRecordingSender::last()
public function last(): ?MessageRecordingSender::count()
public function count(): intStubMethod class
final class StubMethod implements Method StubMethod.php:20
A first factor that passes for the account of a handle: a passkey or an identity provider without their library, so a host's tests (and the core's) drive the conductor and the policy. Proof: ['handle' => ...].
StubMethod::__construct()
public function __construct(readonly Factor $factor, readonly Accounts $accounts)StubMethod::factor()
public function factor(): FactorStubMethod::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictStubSecondFactor class
final class StubSecondFactor implements SecondFactor StubSecondFactor.php:19
A second factor (an authenticator application without otphp) enrolled for the handles given, passing on the code given. Proof: ['code' => ...].
StubSecondFactor::__construct()
public function __construct(readonly Factor $factor, readonly Accounts $accounts, readonly array $enrolled = [], readonly string $code = '123456')$enrolledthe handles that set it up
StubSecondFactor::factor()
public function factor(): FactorStubSecondFactor::enrolled()
public function enrolled(object $account): boolStubSecondFactor::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictTestAccount class
final class TestAccount TestAccount.php:9
An account for tests and first steps, every proof on it: what InMemoryStore reads and writes.
public array $links = []public array $plainLinks = []public array $backupCodes = []public array $identities = []public array $identityDetails = []public string $handlepublic string $identifierpublic ?string $emailpublic ?string $passwordA password_hash() or Symfony hash.
public ?string $namepublic ?string $refusedWhy it may not sign in (banned, locked...), or null.
public ?string $totpSecretpublic bool $emailCodeEnabledpublic ?string $emailCodepublic int $trustedVersion
TestAccount::__construct()
public function __construct(public string $handle, public string $identifier, public ?string $email = null, public ?string $password = null, public ?string $name = null, public ?string $refused = null, public ?string $totpSecret = null, public bool $emailCodeEnabled = false, public ?string $emailCode = null, public int $trustedVersion = 0)Omniauth\TrustedDevice
TrustedDevices class
final class TrustedDevices implements Ambient, Revocable TrustedDevices.php:31
A second factor already passed on this browser: the trusted_device cookie, written in scheb/2fa's very format so the devices trusted before the switch stay trusted - JWTs signed HS256 by the host's secret (Symfony's kernel.secret), claims iat, exp, "usr" (the account's identifier), "fwl" (the firewall), "vsn" (the account's trusted version), several tokens joined by ";". Encoded with PHP's own HMAC, byte for byte what lcobucci/jwt writes for scheb's JwtTokenEncoder (dates with their microseconds), and read as it reads them (signature, iat <= now < exp).
A token counts while its version is the account's: TrustedVersion::revokeAll() (a second factor turned off, a password changed) makes every device fall back.
const LIFETIME = 2592000
TrustedDevices::__construct()
public function __construct(readonly Accounts $accounts, readonly TrustedVersion $versions, readonly string $secret, readonly int $lifetime = self::LIFETIME, ?ClockInterface $clock = null)TrustedDevices::factor()
public function factor(): FactorTrustedDevices::verify()
public function verify(Attempt $attempt, Proof $proof, Context $context): VerdictTrustedDevices::trusts()
public function trusts(object $account, string $cookie, string $firewall = 'main'): boolWhether the cookie holds a valid token of this account, this firewall and the account's version.
TrustedDevices::remember()
public function remember(object $account, Context $context): stringThe cookie's new value with this browser trusted for the account: as scheb's TrustedDeviceTokenStorage writes it - the invalid and expired tokens dropped, those of the account and firewall replaced by a new one, the others kept.
TrustedDevices::revoke()
public function revoke(object $account): voidTrustedDevices::encode()
public function encode(string $username, string $firewall, int $version, \DateTimeImmutable $issuedAt, \DateTimeImmutable $until): stringOne token, as scheb's JwtTokenEncoder::generateToken() makes it with lcobucci/jwt.
TrustedDevices::decode()
public function decode(string $token): ?arrayOne token's claims when it is signed with the key and valid now, as scheb's JwtTokenEncoder::decodeToken() accepts it; null otherwise.