{"id":70,"date":"2014-12-23T12:07:58","date_gmt":"2014-12-23T12:07:58","guid":{"rendered":"http:\/\/smartfoxserver.com\/blog\/?p=70"},"modified":"2018-10-03T07:55:57","modified_gmt":"2018-10-03T07:55:57","slug":"how-to-create-an-extension-based-custom-login","status":"publish","type":"post","link":"https:\/\/smartfoxserver.com\/blog\/how-to-create-an-extension-based-custom-login\/","title":{"rendered":"How to create an Extension based custom login"},"content":{"rendered":"<p>Implementing a custom login on the server-side is a simple process. SFS2X fires the following two login events.<\/p>\n<ul>\n<li><strong>USER_LOGIN<\/strong>: fired when a client requests to join a Zone. Here we\u00a0can validate the client credentials and decide if the User can continue the login process. At this stage the client is represented by a <em>Session<\/em>object, not by an <em>SFSUser<\/em> object yet.<\/li>\n<li><strong>USER_JOIN_ZONE<\/strong>: notified when a client has successfully joined a Zone (and is turned into an SFSUser).<\/li>\n<\/ul>\n<p><!--more--><\/p>\n<p>In order to add our\u00a0custom login logic we\u00a0should follow these two\u00a0steps:<\/p>\n<h3>1) Configure the Zone<\/h3>\n<p>Launch the <strong>AdminTool<\/strong>, open the <a href=\"http:\/\/docs2x.smartfoxserver.com\/GettingStarted\/admintool-ZoneConfigurator\">Zone Configurator<\/a> module and enable your Zone&#8217;s <em>Use custom Login\u00a0<\/em>setting; then restart SFS2X.<\/p>\n<h3>2) Server code<\/h3>\n<p>Create a new server-side Extension that extends the <em>SFSExtension<\/em> class. The\u00a0<strong>init<\/strong>() method should look like this:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n@Override\r\npublic void init()\r\n{\r\n   trace(&quot;My CustomLogin extension starts!&quot;); \r\n\r\n   \/\/ Register for login event\r\n   addEventHandler(SFSEventType.USER_LOGIN, LoginEventHandler.class);\r\n}\r\n<\/pre>\n<p>Now create the <em>LoginEventHandler<\/em> class which will take care of the user name\/password checking. In the following example two specific user names are not allowed to login.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npublic class LoginEventHandler extends BaseServerEventHandler\r\n{\r\n   @Override\r\n   public void handleServerEvent(ISFSEvent event) throws SFSException\r\n   {\r\n      String name = (String) event.getParameter(SFSEventParam.LOGIN_NAME); \r\n\r\n      if (name.equals(&quot;Gonzo&quot;) || name.equals(&quot;Kermit&quot;))\r\n      {\r\n\r\n        \/\/ Create the error code to send to the client\r\n        SFSErrorData errData = new SFSErrorData(SFSErrorCode.LOGIN_BAD_USERNAME);\r\n        errData.addParameter(name);\r\n\r\n        \/\/ Fire a Login exception\r\n        throw new SFSLoginException(&quot;Gonzo and Kermit are not allowed in this Zone!&quot;, errData);\r\n      }\r\n   }\r\n}\r\n<\/pre>\n<p>If one of the two unwanted names is detected, an <strong>SFSException<\/strong> can be fired. In doing so we provide a message that is logged on the server-side and an <strong>SFSErrorData<\/strong> object which contains the error code (<em>SFSErrorCode.LOGIN_BAD_USERNAME<\/em>) and the bad name itself.<\/p>\n<p>Typical error codes used in this context are <em>SFSErrorCode.LOGIN_BAD_USERNAME<\/em> and<em>SFSErrorCode.LOGIN_BAD_PASSWORD<\/em>, both taking an additional parameter which is the wrong name or password.<\/p>\n<p>Now this is a very simple example that just shows how to deny access to users with a name of <strong>Kermit<\/strong> or<strong>Gonzo<\/strong>. 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 <strong>SFSLoginException<\/strong>.<\/p>\n<p>If no exception is thrown the system will accept the user and continue the login process.<br \/>\nThere are other things that could go wrong during this phase, for instance:<\/p>\n<ul>\n<li>the Zone is full and no more logins are allowed;<\/li>\n<li>another user with the same name is already logged in;<\/li>\n<li>the user is currently in the banned list;<\/li>\n<li>the user name might contain bad words that are not acceptable (depending on your custom configuration);<\/li>\n<li>etc.<\/li>\n<\/ul>\n<p>Once all these checks are passed the user is finally logged in the Zone. At this point the server code will receive an <strong>USER_JOIN_ZONE<\/strong> event, if you subscribed to it.<\/p>\n<p>This last step is optional and it won&#8217;t be necessary in many cases.<br \/>\nTypically 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).<\/p>\n<div class=\"dottedYellowBox\">\n<p><strong>TIP:\u00a0<\/strong>When working with asynchronous events such as <strong>USER_LOGIN<\/strong> and <strong>USER_JOIN_ZONE<\/strong> it&#8217;s a bit more difficult to maintain the state of the current transaction\/operation.<\/p>\n<p>A convenient way to maintain the state is to use the user Session object. In particular the <a href=\"\/api-docs\/javadoc\/com\/smartfoxserver\/bitswarm\/sessions\/Session.html\" target=\"_blank\">Session object<\/a> allows to store custom parameters as key-value pairs (see the JavaDoc, methods getProperty\/setProperty, etc).<\/p>\n<\/div>\n<h3>3) Secure passwords<\/h3>\n<p>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.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\ngetApi().checkSecurePassword(session, clearPass, encryptedPass);\r\n<\/pre>\n<p>The method call will return true if the password match and false otherwise.<\/p>\n<p>On the client side there&#8217;s absolutely no difference between a &#8220;standard&#8221; login and a &#8220;custom&#8221; one. All we\u00a0need to do is adding a listener for the <strong>SFSEvent.LOGIN<\/strong> event to receive the server response, and send a <strong>LoginRequest<\/strong> to log into a Zone.<\/p>\n<p>We\u00a0can check the <a href=\"http:\/\/docs2x.smartfoxserver.com\/api-docs\/asdoc\/\" target=\"_blank\">AS3 documentation<\/a> 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 <a href=\"http:\/\/docs2x.smartfoxserver.com\/AdvancedTopics\/privilege-manager\" target=\"_blank\">User Permissions<\/a>.<\/p>\n<h3>4) Change the user name at login time<\/h3>\n<p>There are cases in which we\u00a0need 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\u00a0have stored a\u00a0nickname, which should be used instead.<\/p>\n<p>There is a simple convention that allows us\u00a0to provide an alternative name to the login system. In the <strong>USER_LOGIN<\/strong> event we\u00a0are passed an empty <strong>SFSObject<\/strong> that can be used to return custom data to the client. We\u00a0just need to provide the name in that object under a very specific (and reserved) key name. See the code below:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npublic class LoginEventHandler extends BaseServerEventHandler\r\n{\r\n   @Override\r\n   public void handleServerEvent(ISFSEvent event) throws SFSException\r\n   {\r\n      String name = (String) event.getParameter(SFSEventParam.LOGIN_NAME);\r\n      ISFSObject outData = (ISFSObject) event.getParameter(SFSEventParam.LOGIN_OUT_DATA);\r\n\r\n      \/\/ ...\r\n      \/\/ your login logic goes here\r\n      \/\/ ...\r\n\r\n      \/\/ Provide a new name for the user:\r\n      String newName = &quot;User-&quot; + name;\r\n      outData.putUtfString(SFSConstants.NEW_LOGIN_NAME, newName);\r\n   }\r\n}\r\n<\/pre>\n<h3>5) Set User&#8217;s permissions<\/h3>\n<p>If your application requires different levels of access for different user profiles you can also configure a User&#8217;s permission id during the login phase. There are a number of preset user levels such as &#8220;Guest&#8221;, &#8220;Registered&#8221;, &#8220;Moderator&#8221;, &#8220;Admin&#8221; and custom ones can be added if necessary.<\/p>\n<p>We recommend <a href='http:\/\/docs2x.smartfoxserver.com\/AdvancedTopics\/privilege-manager' target='_blank'>this article<\/a> to learn more about how the Permission Manager works.<\/p>\n<p>During the login phase you can setup the User&#8217;s permissions via this code:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nsession.setProperty(&quot;$permission&quot;, DefaultPermissionProfile.MODERATOR);\r\n<\/pre>\n<p>Now the User permissions are properly configured. Each time a request is sent from the client side the Permission Manager will verify it against the user profile and determine if it can be executed or rejected. In case the request is denied an error will be logged with the details.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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\u00a0can 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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[5],"tags":[7],"_links":{"self":[{"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/posts\/70"}],"collection":[{"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/comments?post=70"}],"version-history":[{"count":12,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/posts\/70\/revisions"}],"predecessor-version":[{"id":983,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/posts\/70\/revisions\/983"}],"wp:attachment":[{"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/media?parent=70"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/categories?post=70"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/tags?post=70"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}