Hopefully readable summary:

  • Everything is a mess, but I think we already knew that.
  • AuthPlugin handles policy questions, actual doing of things (authenticate), and modifying the UI
  • AuthPluginUser is only used by CentralAuth
  • Code in core split across User, LoginForm (SpecialUserlogin), AuthPlugin, AuthPluginUser. Majority of logic in SpecialUserlogin, which API calls internally (ewww)
  • Mixture of hooks and AuthPlugin subclassing
  • Need multiple layers - so you could could have TwoFactorAuth on top of CentralAuth
  • Extensions that just use hooks and modify the UI like OATHAuth don't support the API properly.
  • Hooks like UserLoginComplete which allow for injecting raw HTML are terrible hacks.


  • If no session exists, wfSetupSession is called
  • LoginForm::load() calls $wgAuth->setDomain() if the wpDomain parameter was passed
  • Redirect to HTTPS happens if necessary ($wgSecureLogin/wfCanIPUseHTTPS)
  • Check to make sure the client supports cookies


  • Validate non-empty username, token is valid, and checks login throttle (which is different from the normal MW ping limiter system)
  • Load the current user from the session, invoking the UserLoadFromSession hook which might auto-create the user. Tim calls this hook evil, more on it later.
  • LoginUserMigrated hook is run, which was for CentralAuth to say a user had been renamed
  • If the user still does not exist, LoginForm::attemptAutoCreate is called.


  • Checks to see if User::isBlockedFromCreateAccount(), then if $wgAuth->autoCreate(), $wgAuth->userExists(), and $wgAuth->AbortAutoAccount().
  • AbortAutoAccount hook is called:

* AbuseFilter, Titleblacklist hook in here to stop bad accounts
* CentralAuth hooks in to stop new account creation if a global rename is in progress...why????
* CentralAuthHooks::attemptAddUser() calls this hook and appears to replicate logic from LoginForm (LdapAutoAuthentication::attemptAddUser() also looks similar)

  • Add user object to database, then set email, realname, token
  • Call $wgAuth->initUser()
  • Update SiteStats and add user's userpage+talk page to watchlist

If autocreation failed, we're back in authenticateUserData

  • AbortLogin hook is run, provides User, raw password, abort constant, message key

* OATHAuth - verifies wpOATHAuth token (the way this extension uses hooks feels like a hack that we don't support multiple auth layers)
* CentralAuth - checks user is currently not being globally renamed and is not locked
* ConfirmEdit - verify captcha, if you go over 3 bad logins and fail captcha, claims a bad password was entered
* SSLClientAuthentication - does some weird ORM stuff, but just authenticating with SSL certificates
* TwoFactorAuthentication - either does a redirect to Special:TwoFactorAuth/auth or verifies an external token
* SecureSessions

If hook was not aborted, we check the user's password using User::checkPassword:
* Calls $wgAuth->authenticate(), if it returns false, $wgAuth->strict() and $wgAuth->strictUserAuth() (used by CA) is called to check whether we can use local authentication.
* If $wgAuth didn't do anything, the Password object is checked, and PasswordFactory might also update the password
back in LoginForm::authenticateUserData

  • If password didn't match, there is legacy code to check for a temporary password
  • Check if they're blocked and $wgBlockDisablesLogin (typically true on private wikis)
  • if their password is expired, force a password reset without login
  • If the password *did* match, $wgAuth->updateUser, set $wgUser and the context user. Clear login throttle.
  • If autocreated, call AuthPluginAutoCreate hook (CheckUser and NewUserMessage)
  • Call LoginAuthenticateAudit hook (for good and bad logins), used by ConfirmEdit to clear captcha count, hook also used in WMF prod to log failed logins to sysop accounts

...and we're back in LoginForm::processLogin()!
There's a giant switch statement that processes the return value of authenticateUserData:

  • Success: Invalidate the User's cache, check whether the user requires HTTPS ('prefershttps' preference, UserRequiresHTTPS hook, wfCanIPUseHTTPS()), set some cookies accordingly, clear the login token, reset password throttle, set $wgLang/context lang to user's preferred language (re-implementing a bit of RequestContext logic :/), if "soft" expired password or length too short, show reset form. Calls LoginForm::successfulLogin(), triggers UserLoginComplete hook, which allows for injecting raw HTML into the response...ewwwwww. If no hook adds HTML, the user will be redirected to back where they came from.
  • If it's not sucess, then an error message is picked and formatted to be displayed to the user.