How to create an Extension based custom login

Implementing a custom login on the server-side is a simple process. SFS2X fires the following two login events.

  • USER_LOGIN: fired when a client requests to join a Zone. Here we can validate the client credentials and decide if the User can continue the login process. At this stage the client is represented by a Sessionobject, not by an SFSUser object yet.
  • USER_JOIN_ZONE: notified when a client has successfully joined a Zone (and is turned into an SFSUser).

In order to add our custom login logic we should follow these two steps:

1) Configure the Zone

Launch the AdminTool, open the Zone Configurator module and enable your Zone’s Use custom Login setting; then restart SFS2X.

2) Server code

Create a new server-side Extension that extends the SFSExtension class. The init() method should look like this:

@Override
public void init()
{
   trace("My CustomLogin extension starts!"); 

   // Register for login event
   addEventHandler(SFSEventType.USER_LOGIN, LoginEventHandler.class);
}

Now create the LoginEventHandler class which will take care of the user name/password checking. In the following example two specific user names are not allowed to login.

public class LoginEventHandler extends BaseServerEventHandler
{
   @Override
   public void handleServerEvent(ISFSEvent event) throws SFSException
   {
      String name = (String) event.getParameter(SFSEventParam.LOGIN_NAME); 

      if (name.equals("Gonzo") || name.equals("Kermit"))
      {

        // Create the error code to send to the client
        SFSErrorData errData = new SFSErrorData(SFSErrorCode.LOGIN_BAD_USERNAME);
        errData.addParameter(name);

        // Fire a Login exception
        throw new SFSLoginException("Gonzo and Kermit are not allowed in this Zone!", errData);
      }
   }
}

If one of the two unwanted names is detected, an SFSException can be fired. In doing so we provide a message that is logged on the server-side and an SFSErrorData object which contains the error code (SFSErrorCode.LOGIN_BAD_USERNAME) and the bad name itself.

Typical error codes used in this context are SFSErrorCode.LOGIN_BAD_USERNAME andSFSErrorCode.LOGIN_BAD_PASSWORD, both taking an additional parameter which is the wrong name or password.

Now this is a very simple example that just shows how to deny access to users with a name of Kermit orGonzo. Of course your logic might require a little more sophistication but you should get the idea. When you need to stop the execution of the login process you just throw an SFSLoginException.

If no exception is thrown the system will accept the user and continue the login process.
There are other things that could go wrong during this phase, for instance:

  • the Zone is full and no more logins are allowed;
  • another user with the same name is already logged in;
  • the user is currently in the banned list;
  • the user name might contain bad words that are not acceptable (depending on your custom configuration);
  • etc.

Once all these checks are passed the user is finally logged in the Zone. At this point the server code will receive an USER_JOIN_ZONE event, if you subscribed to it.

This last step is optional and it won’t be necessary in many cases.
Typically you will use this when you need to perform specific actions after the user is logged in the system (like setting User Variables, auto-join a Room, etc).

TIP: When working with asynchronous events such as USER_LOGIN and USER_JOIN_ZONE it’s a bit more difficult to maintain the state of the current transaction/operation.

A convenient way to maintain the state is to use the user Session object. In particular the Session object allows to store custom parameters as key-value pairs (see the JavaDoc, methods getProperty/setProperty, etc).

3) Secure passwords

The user password is never transmitted in clear from the client to the server, for security reasons. In order to be able to compare the encrypted password with your database original password we provide a convenient method in the API.

getApi().checkSecurePassword(session, clearPass, encryptedPass);

The method call will return true if the password match and false otherwise.

On the client side there’s absolutely no difference between a “standard” login and a “custom” one. All we need to do is adding a listener for the SFSEvent.LOGIN event to receive the server response, and send a LoginRequest to log into a Zone.

We can check the AS3 documentation and the examples for all the details on how to do this. For the other languages the process is the same. Another interesting reading, related to this topic, is the discussion about the User Permissions.

4) Change the user name at login time

There are cases in which we need to change the name provided by the user at login time with another one extracted from the database. An example is when the user logs in with an email address as the login name, but on the database we have stored a nickname, which should be used instead.

There is a simple convention that allows us to provide an alternative name to the login system. In the USER_LOGIN event we are passed an empty SFSObject that can be used to return custom data to the client. We just need to provide the name in that object under a very specific (and reserved) key name. See the code below:

public class LoginEventHandler extends BaseServerEventHandler
{
   @Override
   public void handleServerEvent(ISFSEvent event) throws SFSException
   {
      String name = (String) event.getParameter(SFSEventParam.LOGIN_NAME);
      ISFSObject outData = (ISFSObject) event.getParameter(SFSEventParam.LOGIN_OUT_DATA);

      // ...
      // your login logic goes here
      // ...

      // Provide a new name for the user:
      String newName = "User-" + name;
      outData.putUtfString(SFSConstants.NEW_LOGIN_NAME, newName);
   }
}