• Examples (iOS)
• Examples (Java/Android)
• Examples (C++)
Server API Documentation

 

Since 2.7.0

» The Login Assistant component

Almost every application requires a password-based user access and writing the code that handles the client parameters and checks them against the database is pretty tedious and repetitive.

The Login Assistant component is a helper class that assist developers in creating a database-driven login system, without the hassle of writing your own database access code.

In a nutshell this is how it works:

  1. configure the DBManager in your application's Zone
  2. instantiate the component in the init() method of your Extension
  3. configure the component

» Deploying the Login Assistant component

If you are using SmartFoxServer 2X v2.7.0 or later, skip this step as the component is already available.
Otherwise, in order to activate the component in your SmartFoxServer 2X installation, all you need to do is:

» Basic usage

Before you get started, make sure to configure your database access in the Zone configuration. In order to do so, open the SFS2X AdminTool, launch the Zone Configurator module and select the Database Manager tab. You can find more details on how to proceed in this recipe.

For this example we will use a simple users table with the following fields:

id name pword email
1 Kermit thefrog kermit@muppets.com
2 Gonzo thegreat gonzo@muppets.com
3 Fozzie thebear fozzie@muppets.com
4 MissPiggy thepig piggy@muppets.com

Next step we create a new Extension and use this code in the init() method:

	public class MuppetsExtension extends SFSExtension
	{
		private LoginAssistantComponent lac;
		
		@Override
		public void init()
		{
			lac = new LoginAssistantComponent(this);
			
			// Configure the component
			lac.getConfig().loginTable = "muppets";
			lac.getConfig().userNameField = "name";
			lac.getConfig().passwordField = "pword";
		}
		
		public void destroy()
		{
			lac.destroy();
		}
	}

All we have done is simply configuring the component by specifying the name of the table to be used and the names of the fields that represent the user name and password. VoilĂ ! The component is now configured to use the correct fields and will take care of checking the credentials, dispatching errors and sanitizing user data against SQL injections.

» A slightly more advanced scenario

Let's suppose we have a slightly more complicated scenario where users will login with an email address but their user name in the system must be associated with the name field in the database. Also we would like to make sure that the user name check is case-sensitive.

In fact, by default most databases will compare strings in a non case-sensitive way, unless a specific collation is used. The Login Assistant can be configured to use a specific field in the DB as the login name, let's see how this is done:

	@Override
	public void init()
	{
	    lac = new LoginAssistantComponent(this);
	    
	    // Configure the component
	    lac.getConfig().loginTable = "muppets";
	    lac.getConfig().userNameField = "email";
	    lac.getConfig().passwordField = "pword";
	
	    lac.getConfig().nickNameField = "name";
	    lac.getConfig().useCaseSensitiveNameChecks = true;
	}

All it takes is just a couple of extra lines of code to indicate the nick name field and turning on the case-sensitive check.

NOTE
If you are interested in learning more about database string equality and collations, please check these external resources:

» StackOverflow: SQL Case Sensitive String Compare
» MySQL: Case Sensitivity in String Searches

» Advanced features

Let's try to complicate things some more. Suppose our database contains a couple of extra fields: an avatar image and a flag indicating whether the user has moderator privileges.

id name pword email avatar isMod
1 Kermit thefrog kermit@muppets.com kermit.jpg Y
2 Gonzo thegreat gonzo@muppets.com gonzo.jpg N
3 Fozzie thebear fozzie@muppets.com fozzie.png Y
4 Miss Piggy thepig piggy@muppets.com piggy.png N

We will store the image name inside the client's Session object, so that when we receive the USER_JOIN_ZONE event, we'll be able to set the image via User Variables. Also we want to be able to set the client as moderator, if the database flag is set.

The Login Assistant provides an easy way to add custom logic before and after the credentials have been checked, via the preProcessPlugin and postProcessPlugin classes. Let's see how this works:

	@Override
	public void init()
	{
		lac = new LoginAssistantComponent(this);
		
		// Configure the component
		lac.getConfig().loginTable = "muppets";
		lac.getConfig().userNameField = "email";
		lac.getConfig().passwordField = "pword";
		lac.getConfig().nickNameField = "name";
		lac.getConfig().useCaseSensitiveNameChecks = true;
	
		lac.getConfig().extraFields = Arrays.asList("avatar", "isMod");
		
		lac.getConfig().postProcessPlugin = new ILoginAssistantPlugin ()
		{
			public void execute(LoginData loginData)
			{
				ISFSObject fields = loginData.extraFields;
				
				String avatarPic = fields.getUtfString("avatar");
				boolean isMod = fields.getUtfString("isMod").equalsIgnoreCase("Y");
				
				// Store avatar in session object
				loginData.session.setProperty("avatar", avatarPic)
				
				// Set client as Moderator
				if (isMod)
					loginData.session.setProperty("$permission", DefaultPermissionProfile.MODERATOR);
			}
		};
	}

We have added a new configuration parameter called extraFields, which contains a list of custom field names we want to select at login time.

In the next line we have passed the postProcessPlugin class by declaring it inline, as an anonymous class for convenience. The class is required to implement the ILoginAssistantPlugin interface which declares an execute() method.

As you can see in this method we are passed all the relevant login data, including all the fields extracted from the database (as an SFSObject) and the client's Session. This way it is very simple to apply additional logic to the login process without having to write a custom Login handler.

You can find all the details about the classes used in this example by consulting the server-side javadoc under the package com.smartfoxserver.v2.components.login.

Since 2.10.0

 

» Custom password checking and encryption

Since SmartFoxServer 2X 2.10 we have added TLS encryption to the protocol which opens the door to custom handling of the password check.

Before protocol encryption (version < 2.10) only the password was encrypted in the LoginRequest using the Challenge Handshake Authentication Protocol (CHAP), by using a hashing algorithm. This renders the password very secure during the transmission but doesn't allow developer to use password salting for the storage in the database.

With protocol encryption the password can be sent without hashing, bypassing the CHAP and allowing the server Extension to receive the original password, which can then be salted and stored as such in the database.

To bypass the salt you need to use SmartFoxServer 2X 2.10 (or higher) and set the LoginAssistant's customPasswordCheck flag to true in the configuration.

	@Override
	public void init()
	{
		lac = new LoginAssistantComponent(this);
		
		// Configure the component
		lac.getConfig().loginTable = "muppets";
		
	    ...
	    
		lac.getConfig().customPasswordCheck = true;
	}

This in turn allows us to send the password as a field of the custom SFSObject in the LoginRequest and handle it in the PreProcess plugin.

This is how the client should send the password:

	ISFSObject sfso = SFSObject.NewInstance();
    sfso.PutUtfString("passwd", tf_password.text);
    
    sfs.Send(new Sfs2X.Requests.LoginRequest(ti_userName.text, "", sfs.Config.Zone, sfso));
	var sfso = new SFS2X.SFSObject();
	sfso.putUtfString("passwd", document.getElementById("tf_password").value);
	
	sfs.send(new SFS2X.LoginRequest(document.getElementById("tf_userName").value, "", sfso, sfs.config.zone));
	var sfso:ISFSObject = new SFSObject();
	sfso.putUtfString("passwd", tf_password.text);
	
	sfs.send(new LoginRequest(ti_userName.text, "", sfs.config.zone, sfso));

It is important to note that we are sending the password as a custom field of the optional SFSObject in the LoginRequest. The field name can be any identifier as long as the same string is used both from client and server side.

On the server side we must take care of validating the password in the PreProcess plugin:

	class LoginPreProcess implements ILoginAssistantPlugin
	{
		@Override
		public void execute(LoginData ld)
		{
			String clientPass = ld.clientIncomingData.getUtfString("passwd");
			
			// Let's see if the password from the DB matches that of the user 
			if (!ld.password.equals(clientpass))
				throw new PasswordCheckException();
				
			// Success!
		}
	}

This is pretty straightforward. We obtain the password using the key name we have chosen, from the optional SFSObject, and check it against the password from the DB. If they don't match we just need to raise the PasswordCheckException to interrupt the flow. This in turn will notify the client of using the regular LOGIN_ERROR event.

Otherwise if the condition is satisfied we're done. The LoginAssistant flow will continue with the rest of its logic and the client will be logged in.

NOTE
If you are going to use this new modality we strongly suggest to activate the useEncryption flag in your Zone configuration (AdminTool > Zone Configurator) to make sure that the server will only accept encrypted connections, thus hiding the communication from possible eavedroppers.

» Handling errors

The errors related to wrong credentials, banned access, duplicate user names, etc, are all handled by the Login Assistant component using the standard SmartFoxServer 2X rules. This means that all error messages can be customized on the client-side via the SFSErrorCodes class.

The only place where the developer is responsible for handling exceptions is in the pre-process and post-process plugins. Any runtime exception in those classes will travel up to the top of the SFS2X API thread and be captured and logged there. This also means that they will interrupt the login flow, therefore it is up to you to decide which exceptions needs to be caught (in order not to interrupt the flow) and which can stop the process.