bw logo

Chapter 21. User Authentication and Billing System Integration

In the document Server Overview's section Design Introduction Use Cases Logging In, an overview of the login process is given. This chapter details the part of that process concerned with authenticate the login details and loading the initial entity.

21.1. Authentication by DBMgr

By default, authentication of the player's username and password is done by the DBMgr. It also handles the mapping of that username to the initial entity the player will use. There are a number of different ways this can be done. This section discusses these different approaches.

21.1.1. Default Authentication via MySQL

When using the MySQL database, DBMgr uses the bigworldLogOnMapping table (for details, see Entity Tables) to check login's validity and which entity to load for the user. It contains the following columns:

  • logOnName

    Username used to log in to BigWorld. This corresponds to the username argument passed to the BigWorld.connect() call on the client.

    This is the primary key.

  • password

    User's password. This corresponds to the password argument passed to the BigWorld.connect() call on the client.

    If bw.xml file's billingSystem/isPasswordHashed setting is true, the password will be hashed. The following SQL is used to perform the hashing:

    MD5( CONCAT( password, logOnName ) )
  • entityType

    Type ID of the entity to create. To map the type ID to its name, bigworldEntityTypes table (for details, see Entity Tables.) is used. This can be queried with the following MySQL query.

    SELECT typeID FROM bigworldEntityTypes WHERE name='MyEntityType';
  • entityID

    The database id of the associated entity.

To determine whether a login is valid and which entity to load, DBMgr executes the following steps:

  1. Find a row in the bigworldLogOnMapping table where the logOnName column matches the username.

  2. Check that the user's password matches the password of the found row.

  3. Load the entity identified by typeID and recordName.

For this step to succeed, the entity must already exist in the database. For information on how to create an entity and write it to the database, see Reading and Writing Entities.

This table is usually populated using MySQL tools. An example script, bigworld/tools/server/misc/add_account.py, implements a command line utility that can be used to create accounts.

For information on how BigWorld can automatically populate this table, see Accepting All Users.

21.1.2. Default Authentication via XML

Note

It is highly recommended to use the MySQL database back-end for production environments. You should consider moving away from the XML database before implementing user authentication.

The XML database implements the equivalent of the bigworldLogOnMapping table in the section _BigWorldInfo/LogOnMapping. Each row is specified using a separate sub-section, as illustrated below:

<root>
  <_BigWorldInfo>
    <LogOnMapping>
      <item>
        <logOnName>  John      </logOnName>
        <password>   acde1234  </password>
        <type>       Account   </type>
        <entityID>   7231  </entityID>
      </item>
      <item>
        <logOnName>  Peter     </logOnName>
        <password>   zyxw9876  </password>
        <type>       Avatar    </type>
        <entityID>   7232  </entityID>
      </item>
    </LogOnMapping>
  </_BigWorldInfo>
...
</root>

bigworldLogOnMapping table in XML

In the example above, two rows are specified. The tags correspond to the table columns as described below:

  • logOnName Corresponds to column: logOnName

  • password Corresponds to column: password

  • type Corresponds to column: typeID

  • entityID Corresponds to column: entityID

When specifying the type of the entity, the type name is used instead of the type ID used by the MySQL database.

Please note that <res>/scripts/db.xml is only read and written to during startup and shutdown, respectively. Therefore, it is not possible to update the _BigWorldInfo/LogOnMapping section and have the changes take effect while DBMgr is running.

21.1.3. Custom Authentication and Billing System Integration

It is often the case that user accounts are stored in an external system. This section describes how to bypass the bigworldLogOnMapping table and use your own system.

21.1.3.1. Billing System Integration using Python

The easiest way to achieve this is via Python. When the DBMgr checks whether to use Python by attempting to call BWPersonality.connectToBillingSystem(). If this exists and returns a Python object, this object is used to perform user authentication. The object should implement a getEntityKeyForAccount and optionally setEntityKeyForAccount.

class BillingSystem( object ):
    def getEntityKeyForAccount( username, password, clientAddr, response ):
        ...

    def setEntityKeyForAccount( username, password, entityType, entityID ):
        ...

def connectToBillingSystem():
    return BillingSystem()

Example excerpt from res/scripts/db/BWPersonality.py

The getEntityKeyForAccount method accepts username, password, client address and response object as arguments. The username and password should be used for authentication and an appropriate response should be returned by calling one of the methods of the handler object.

The response argument has the following methods:

  • loadEntity( entityType, entityDatabaseID ) This should be called on successful authentication. It accepts two arguments, the type and database id of the entity to be loaded from the database and used by this user.

  • loadEntityByName( entityType, entityName, shouldCreateUnknown ) This is like loadEntity but takes the name (identifier string) of the entity instead of the database id. It also takes a boolean argument indicating whether to create a new entity if one with this name does not exist.

  • createNewEntity( entityType, shouldRemember ) This should be called when logging in should proceed but a new entity should be created instead of loading one from the database. It accepts two argument, the type of the entity to create and a boolean indicating whether this new entity should be saved in the database and in the billing system.

  • failureInvalidPassword() This should be called when authentication fails because of an invalid password.

  • failureNoSuchUser() This should be called when authentication fails because no such user exists.

You may not want to differentiate between receiving an invalid password or non-existent user. If so, you should just consistently return either failureNoSuchUser or failureInvalidPassword on failure.

The setEntityKeyForAccount is only required if createNewEntity is ever called with shouldRemember as True. If so, this is called with the username and password of the account and the entity type and entity id of the entity to associate with that account.

FantasyDemo has a simple example where account information is stored in an sqlite database. This is located in fantasydemo/res/scripts/db/FantasyDemo.py and fantasydemo/res/scripts/db/sqlite_billing.py.

21.1.3.2. Billing System Integration using C++

Billing system integration can also be done via C++. A skeleton billing system case is implemented in bigworld/src/server/dbmgr/custom_billing_system.cpp. Integration should be done by filling out this class. To make use of this class, USE_CUSTOM_BILLING_SYSTEM needs to be set to 1 in bigworld/src/server/dbmgr/Makefile.

The support for Python billing system integration is implemented in bigworld/src/server/dbmgr/py_billing_system.cpp. This can be used as an example.

21.1.4. Accepting All Users

During development, it is often convenient to grant all users access to the server, without having to set up a bigworldLogOnMapping table.

This can be achieved by setting bw.xml file's billingSystem/shouldAcceptUnknownUsers[36] configuration option to true. With this configuration, a default entity is created for the user if there is no row matching the username in bigworldLogOnMapping.

The type of the entity created is controlled by the bw.xml file's billingSystem/entityTypeForUnknownUsers option. Additionally, if billingSystem/shouldRememberUnknownUsers is set to true, then the entity created for the unknown user is saved in the database, and an entry is added in bigworldLogOnMapping. This effectively adds a new user into the system, and subsequent logins by the same user will be processed via the normal login process.

If a custom billing system is being used, it is up to its implementation whether these options are respected.

21.2. Authentication via a Base entity

An alternative method for performing user authentication, and entity selection is to delegate to an account entity and perform the logic in its base entity script. An account entity is just another BigWorld entity that is implemented using Python script. The DBMgr can be configured to bypass its usual login processing and pass control over to the account entity.

To use this method, the following configuration options must be set:

  • billingSystem/entityTypeForUnknownUsersA Required value: Your account entity type

    Reason for required value:

    Type of the entity that will handle the login process.

    For details on how to implement entities, see Directory Structure for Entity Scripting.

  • billingSystem/authenticateViaBaseEntityA Required value: true

    Reason for required value:

    This bypasses the DBMgr authentication and always creates a Proxy entity of the type indicated above. If this entity already exists in the database with its identifier equal to the current username, that data will be loaded otherwise a new entity will be created. New entities are not immediately added to the database. It is up to the base entity script to write only the appropriate entities with a call to Base.writeToDB().

    Note

    The name of an entity is a property tagged with the <Identifier> tag. For details, see The Identifier Tag.

A For details, see the document Server Operations Guide's section Server Configuration with bw.xml DBMgr Configuration Options.

After a successful login:

  • On the client The account's __init__ method is called.

  • On the BaseApp The account's onEntitiesEnabled method is called.

There are a couple of approaches to retrieving the user's password:

  • On the BaseApp, if the account entity has a property called password, it is automatically set to the user's login password. And, of course, the account's name property will try to match the username.

  • Since the login password sent by the client using BigWorld.connect method is usually sent in clear text, it may be preferable to use an empty password for normal login processing. Then, after the account entity has been created, the client can send an encrypted password to the Proxy.

It is up to the account entity's implementation to verify the user's password. This is usually done by communicating with a third-party billing system. For details on how to communicate with a non-BigWorld process, see Non-Blocking Socket I/O Using Mercury. If the user's password is invalid, then the Proxy entity can destroy itself to disconnect the client.