Hướng dẫn:SessionManager và AuthManager

This page is a translated version of the page Manual:SessionManager and AuthManager and the translation is 1% complete.

SessionManager and AuthManager are two authentication-related frameworks introduced in MediaWiki 1.27 that are intended to replace the "there can be only one!" nature of AuthPlugin (the previous authentication system), the inability to do federated login (e.g., "log in with your Google account") without using various hooks to hack around MediaWiki's focus on password-based authentication, and the inability to do session management with anything other than cookies without again using various hooks to hack around MediaWiki's reliance on PHP's cookie-based sessions.

SessionManager is concerned with taking an incoming request and connecting it with a user-session, i.e. the fact that the request is from a logged-in user (or not) and any data that should persist across requests like PHP's $_SESSION. Usually this means cookies holding the "session key" and the user's ID and token, but it could as easily be based on credentials in OAuth headers or a TLS client certificate.

AuthManager is concerned with logging in by supplying credentials to Special:UserLogin or the like, and with creating accounts with new credentials. This includes the usual username and password form, but also includes mechanisms such as logging in via Google, Facebook, or other third-party authentication service; multi-factor authentication; and other login-time checks such as password resets, CAPTCHAs, and so on.

How do I use them?

As a system administrator

The available session providers are configured via $wgSessionProviders . See documentation of that global and of the individual session providers for details.

The available authentication providers are configured via $wgAuthManagerConfig . See documentation of that global and of the individual authentication providers for details.

To handle operations where the user would traditionally be asked to enter their current password for confirmation, AuthManager instead requires that the user have actively logged in recently during the current session. To configure what exactly is meant by "recently", use $wgReauthenticateTime to set time limits for each operation. To determine what happens when the user is using something like OAuth where each request is individually authenticated and "log in" isn't possible, use $wgAllowSecuritySensitiveOperationIfCannotReauthenticate .

As a consumer

Sessions

To access the current session from your code, the most straightforward mechanism is to use the getSession() method on your WebRequest object. The resulting MediaWiki\Session\Session object has methods to access the data that, in the past, you'd have accessed using $webRequest->getSessionData() and ->setSessionData() or by directly accessing $_SESSION.

To test if a session is currently active where you would have previously used code like session_id() !== '', fetch the Session object and use its ->isPersistent() method. To ensure that the session is persistent, fetch it and use its ->persist() method instead of using wfSetupSession().

If you need to access session data in a context other than a client request, e.g. if you saved the session ID to run a background job and need to update status in the session data, use MediaWiki\Session\SessionManager::singleton()->getSessionById().

Authentication

If you are performing a security-sensitive action that would traditionally request the user's password for verification, use MediaWiki\Auth\AuthManager::singleton()->securitySensitiveOperationStatus() to check whether the user has authenticated recently enough, and if necessary direct them through Special:UserLogin to re-authenticate. SpecialPage can handle this for you if you return non-false from SpecialPage::getLoginSecurityLevel().

Other than that, most "consumer" code won't need to interact with AuthManager directly, since you really shouldn't be implementing your own authentication flow. To determine if a user is logged in, use the usual methods such as the getUser() on an IContextSource. To see that a user is authenticated, have them log in via the usual method.

But if you really insist: Authentication in AuthManager is a multi-step process that goes something like this:

  1. Call $authManager->canAuthenticateNow() to determine if authentication is even possible.
  2. Call $authManager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ) to determine which AuthenticationRequest subclasses are needed, convert them to form fields, and present them to the user.
  3. Once the user submits the form, use AuthenticationRequest::loadRequestsFromSubmission() to populate the requests with data and pass them to $authManager->beginAuthentication().
  4. Take action depending on the status of the AuthenticationResponse:
    • For status UI, convert the needed AuthenticationRequests to form fields, present to the user, and on submission use AuthenticationRequest::loadRequestsFromSubmission() to populate the new requests with data and pass them to $authManager->continueAuthentication() and handle the new response.
    • For status REDIRECT, redirect the user to the specified URL. Eventually the user should be redirected back, at which point the needed AuthenticationRequests should be populated with data and passed to $authManager->continueAuthentication().
    • For status PASS, authentication has succeeded.
    • For status RESTART, authentication with a third-party service has succeeded but those credentials aren't associated with a local account. Treat this as either UI or FAIL.
    • For status FAIL, authentication has failed. The response might contain an additional AuthenticationRequest that may be passed into $authManager->beginAuthentication() or $authManager->beginAccountCreation() to preserve state.

The processes for account creation and linking of third-party credentials are similar.

As a provider

If you are writing code that is going to provide new functionality for SessionManager or AuthManager, first you need to decide what kind of thing you are providing:

  • If you are implementing a new way for taking a WebRequest and determining the authenticated user and session data from it, you want to implement MediaWiki\Session\SessionProvider.
    • If you are doing something that seems like logging in, a SessionProvider is not what you want. You probably want a PrimaryAuthenticationProvider instead.
  • If you want to generically add additional checks to the process of getting a Session from a WebRequest, you will likely want to look at the SessionMetadata and SessionCheckInfo hooks. The first allows adding additional metadata to be saved with the session, while the second allows you to look at the metadata when a Session is being loaded and if necessary veto its use.
  • If you are implementing a new way to log in, you want to implement MediaWiki\Auth\PrimaryAuthenticationProvider.
    • If your thing isn't so much a way to log in as a new way to just "be" logged in such as OAuth or TLS client certificates, you don't want this. You probably want a SessionProvider instead.
  • If you are implementing some new check that someone has to pass before being able to log in, such as a CAPTCHA, you want to implement MediaWiki\Auth\PreAuthenticationProvider.
  • If you are implementing something that should happen after logging in, including testing of a second factor, you want to implement MediaWiki\Auth\SecondaryAuthenticationProvider.

In all cases, providers are constructed using ObjectFactory from $wgSessionProviders or $wgAuthManagerAutoConfig and then have dependencies injected by setter methods.

SessionProvider

Class documentation: MediaWiki\Session\SessionProvider

There are broadly three types of session providers, based on two abilities:

  • The ability to save any randomly-generated session ID, versus the ID being based on properties of the request that cannot be changed.
  • The ability to be associated with any arbitrary user who logs in, versus having a user that is intrinsically associated with the request.
    • This latter property is sometimes referred to as making the session "mutable" versus "immutable".

The resulting three types are:

  • Providers that can save any randomly-generated session ID, and can also be associated with any arbitrary user. A prime example of these are the traditional cookie-based sessions.
    • It's not necessary that the provider actually does anything to save the user's identity; as long as the session ID is provided, the user can be determined from the saved metadata.
  • Providers that have a user and session ID intrinsically associated with the request. Good examples here are OAuth and TLS client certificates: the OAuth headers or TLS client certificate are associated with a particular user and you can't just arbitrarily change them to be associated with a different user.
  • Providers that have a user intrinsically associated with the request, but can save any randomly-generated session ID. An example might be HTTP Digest authentication using the "opaque" field to hold the session ID.
    • You can often take a method with an intrinsic user and no ability to save randomly-generated session IDs and use a cookie to give it that ability; the MediaWiki\Session\ImmutableSessionProviderWithCookie base class implements the cookie portion of such a provider.

It's also possible to have a provider with the ability to be associated with any arbitrary user but lacking the ability to save a randomly-generated session ID. This doesn't seem to make any sense, though.

A session provider "provides" sessions by returning a MediaWiki\Session\SessionInfo object from either provideSessionInfo() (for the session associated with a WebRequest) or newSessionInfo() (for a new session not yet associated with any WebRequest). The properties of the SessionInfo are:

  • The priority of the SessionInfo: if by some chance multiple providers find session data in the same request, the one with the highest priority is used. If there is a tie, SessionManager will throw an error. Generally a provider will either always use SessionInfo::MAX_PRIORITY (i.e. "you must use this SessionInfo") or will take the desired priority as a parameter to its constructor.
  • The session ID. It's ok for a provider to provide a SessionInfo without providing an ID if it does identify a user.
  • Whether the request identifies a user, and if that user is authenticated or just named. Using cookie-based sessions as an example,
    • "No user" results when the UserID or UserName cookie isn't present in the request. The user will be determined from the saved session metadata.
    • "Unauthenticated user" results when the UserID or UserName cookie is present, but the "Token" cookie is missing. Any user's ID or name is publicly available, so without the token we can't know that this isn't a spoofing attempt.
      • This is still worthwhile, since we can check that the saved session metadata identifies the same user to prevent someone from using a session ID that has expired and been reassigned to a different user.
    • "Authenticated user" results when the UserID or UserName cookie is present and the "Token" cookie is also present, and hash_equals( $user->getToken(), $cookieToken ) returns true. Since the token is supposed to be secret, we can assume that a correct token means this is the known user.
      • Tokens that come in from the WebRequest must always be checked using hash_equals to avoid timing attacks.
    • There's no case where the UserID or UserName cookie and the "Token" cookie are present, but hash_equals( $user->getToken(), $cookieToken ) returns false. If this happens, the provider must not provide any SessionInfo.
  • Whether various bits were actually found in the WebRequest: the session ID, the user's token, and the forceHTTPS flag.
  • Any additional metadata the provider wants to associate with the session. This is often useful if the provider does authorisation (e.g. OAuth grants) in addition to authentication.

The session provider is also responsible for "persisting" the session to a WebResponse, which is done on a call to persistSession(). If the provider has the ability to save randomly-generated session IDs, it does so here. If it has the ability to independently save the user identity, it should always save the user's ID or name but must only save the user's token if the session's ->shouldRememberUser() method returns true.

For proper caching behavior, the session provider needs to use getVaryHeaders() and getVaryCookies() to indicate which request headers and cookies it uses to determine the SessionInfo. It also needs to provide messages to identify its sessions in error messages, and to provide a hint as to why sessions aren't being persisted when they should be (e.g. that the browser is set to reject cookies).

PreAuthenticationProvider

Class documentation: MediaWiki\Auth\PreAuthenticationProvider, MediaWiki\Auth\AbstractPreAuthenticationProvider

A "pre"-authentication provider can provide fields for the login or account creation form, and perform checks on a login or account creation attempt before any actual authentication happens. For example, it might throttle login attempts by IP address or present a CAPTCHA that must be solved in order to create the account.

PrimaryAuthenticationProvider

Class documentation: MediaWiki\Auth\PrimaryAuthenticationProvider, MediaWiki\Auth\AbstractPrimaryAuthenticationProvider

A "primary" authentication provider is responsible for actual user authentication:

  • Taking input from the login form, determining which user is trying to log in, and actually authenticating that user.
  • Ensuring that the user being created will be able to log in later.
  • Testing whether a user name already exists in the authentication backend storage.
  • Changing the authentication data associated with an existing user, or adding and removing associations with third-party authentication provider accounts.

AuthManager may be configured to use multiple primary authentication providers. On the initial form submission each primary provider in turn is tried, and if it returns an ABSTAIN result the next in line is tried until either one returns a non-ABSTAIN response or it runs out of providers (in which case it generates a FAIL).

The authentication process for a provider is also multi-step: non-ABSTAIN responses include the usual PASS and FAIL, but also UI to present an additional input form for the user to fill out and REDIRECT to send the browser to some third party which should eventually redirect back to continue the authentication process.

SecondaryAuthenticationProvider

Class documentation: MediaWiki\Auth\SecondaryAuthenticationProvider, MediaWiki\Auth\AbstractSecondaryAuthenticationProvider

A "secondary" authentication provider is called after the user has been authenticated by a "primary". For example, it might request a second factor for multi-factor authentication, prompt the user to change their password, or check that the user isn't blocked. Secondary providers are also called on account creation so they can do things such as prompt to set up multi-factor authentication.

AuthManager may be configured with multiple secondary authentication providers, in which case all are run and must PASS or ABSTAIN for the authentication process to succeed. Like primary providers, they may respond with UI and REDIRECT to interact with the user.

Inter-authentication-provider communication

AuthManager provides support for communication between provider modules during the authentication process.

For example, password-based primary authentication providers that have the concept of password expiry can call $this->getManager()->setAuthenticationSessionData( 'reset-pass', $data ), which will cause the secondary authentication provider MediaWiki\Auth\ResetPasswordSecondaryAuthenticationProvider to prompt the user to change their password. It's up to each provider to document which authentication data "keys" it uses and what data it needs to be provided to do its job.

See also

Code stewardship