{"id":642,"date":"2014-12-23T12:08:14","date_gmt":"2014-12-23T12:08:14","guid":{"rendered":"http:\/\/smartfoxserver.com\/blog\/?p=642"},"modified":"2017-04-18T10:45:43","modified_gmt":"2017-04-18T10:45:43","slug":"custom-login-with-database","status":"publish","type":"post","link":"https:\/\/smartfoxserver.com\/blog\/custom-login-with-database\/","title":{"rendered":"Custom login with database"},"content":{"rendered":"<p>This recipe discusses the common scenario in which you need to validate the client credentials against the user profiles stored in a database. We will also show how to execute more custom logic right after the login, for example setting User Variables and joining a Room from server-side.<!--more--><\/p>\n<div class=\"dottedYellowBox\">We use MySQL for this recipe, but you can easily use any other database of your choice (read <a title=\"How to setup a connection to an external Database\" href=\"http:\/\/smartfoxserver.com\/blog\/how-to-setup-a-connection-to-an-external-database\/\">this tutorial<\/a>). We use standard SQL in all code so it can be ported to any other RDBMS.<\/div>\n<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 you can validate the client credentials and decide if the user can continue the login process. At this stage the client is represented as a Session object, not as an <strong>SFSUser<\/strong> yet.<\/li>\n<li><strong>USER_JOIN_ZONE<\/strong>: notified when a client has successfully joined a Zone (and it was turned into an <strong>SFSUser<\/strong>).<\/li>\n<\/ul>\n<p>The example code linked at the bottom of this article comes with a MySQL dump that can be used to generate the database table used by the Extension. These are the steps to setup and test the example application.<\/p>\n<ul>\n<li>We need a database called <strong>sfs2x<\/strong>, if you don&#8217;t have one please create it.<\/li>\n<li>Use the provided <em>muppets-table.sql<\/em> file to import the &#8216;muppets&#8217; table into the database. This contains a few login accounts that you can use to test the login.<\/li>\n<li>Deploy the Extension by copying it in a subfolder of <strong>{sfs-install-dir}\/SFS2X\/extensions\/<\/strong>. You can choose any name for the subfolder, for example <strong>muppetsExt<\/strong>.<\/li>\n<li>Launch the AdminTool, choose the <a href=\"http:\/\/docs2x.smartfoxserver.com\/GettingStarted\/admintool-ZoneConfigurator\" target=\"_blank\">Zone Configurator<\/a> module and edit the <em>BasicExamples<\/em> zone. Set the Extension for the Zone as follows:\n<ul>\n<li><em>Name<\/em>: muppetsExt (or the name you have chosen, if different)<\/li>\n<li><em>Type<\/em>: JAVA<\/li>\n<li><em>Main class<\/em>: sfs2x.extension.test.dblogin.DBLogin<\/li>\n<\/ul>\n<\/li>\n<li>In the <em>General<\/em> tab of the Zone Configurator make sure to activate the<strong> custom login<\/strong> option.<\/li>\n<li>At this point you should be ready to restart SFS2X, unless you also need to setup the database connection. If you haven&#8217;t done this previously we suggest to <a title=\"How to setup a connection to an external Database\" href=\"http:\/\/smartfoxserver.com\/blog\/how-to-setup-a-connection-to-an-external-database\/\">follow this other recipe<\/a>.<\/li>\n<\/ul>\n<p>You can now startup the provided client and test the Extension.<\/p>\n<h3><strong>\u00bb The login handler<\/strong><\/h3>\n<p>The <strong>LoginEventHandler<\/strong> class is where we check the credentials. In this example we access the DBManager connection and use a <strong>PreparedStatement<\/strong> object in order to build the query with parameters and sanitize possible bad characters in the external arguments.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n\/\/ Grab a connection from the DBManager connection pool\r\nconnection = dbManager.getConnection();\r\n\r\n\/\/ Build a prepared statement\r\nPreparedStatement stmt = connection.prepareStatement(&quot;SELECT pword,id FROM muppets WHERE name=?&quot;);\r\nstmt.setString(1, userName);\r\n\r\n\/\/ Execute query\r\nResultSet res = stmt.executeQuery();\r\n<\/pre>\n<p>As an alternative to this approach you could use the <strong>DBManager.executeQuery(String sql, Object[] params) <\/strong>method, which works similarly without the need to work at the connection level. We didn&#8217;t use it for this example because at the time of writing it wasn&#8217;t available yet (requires SmartFoxServer 2X version RC1b or later).<\/p>\n<p>When an errror is encountered (e.g. bad password), we raise an exception of type <strong>SFSLoginException<\/strong> and provide an additional error code:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nif (!getApi().checkSecurePassword(session, dbPword, cryptedPass))\r\n{\r\n    SFSErrorData data = new SFSErrorData(SFSErrorCode.LOGIN_BAD_PASSWORD);\r\n    data.addParameter(userName);\r\n\r\n    throw new SFSLoginException(&quot;Login failed for user: &quot;  + userName, data);\r\n}\r\n<\/pre>\n<p>In the case of a bad username or bad password we also specify the name or password used so that this is reported back to the client.<\/p>\n<h3><strong>\u00bb Handling exceptions<\/strong><\/h3>\n<p>One important aspect to keep in mind when working with a database is proper handling of exceptions. Any call to the database might throw an error if there is a problem with the SQL syntax etc&#8230;, so it is critical to make sure that the connection is closed in any cas,e otherwise it will leak and eventually exahust the connection pool. The best way to handle exceptions is the following:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nry\r\n{\r\n    connection = dbManager.getConnection();\r\n\r\n    \/\/ Your database code goes here\r\n    ...\r\n}\r\ncatch(SQLException sqle)\r\n{\r\n    \/\/ Here we handle the SQL failure\r\n    ...\r\n}\r\nfinally\r\n{\r\n    try\r\n    {\r\n        connection.close();\r\n    }\r\n    catch(SQLException sqle)\r\n    {\r\n        \/\/ It shouldn't happen, but if it does it's best to leave a trace in the logs\r\n    }\r\n}\r\n<\/pre>\n<p>The <em>finally<\/em> block ensures that the connection gets closed in any case before leaving the method, and the additional <em>try\/catch<\/em> block around the <em>close()<\/em> method handles the rare possibility that an error occurs when the connection is returned to the pool.<\/p>\n<h3><strong>\u00bb Post login handler<\/strong><\/h3>\n<p>When the <strong>USER_JOIN_ZONE<\/strong> event is notified we are ready to do more work with the user, which is now fully logged in the system. The <strong>ZoneJoinEventHandler<\/strong> class sets the &#8220;dbID&#8221; User Variable with the user id coming from the database and finally joins the user in the main lobby Room.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\npublic class ZoneJoinEventHandler extends BaseServerEventHandler\r\n{\r\n    @Override\r\n    public void handleServerEvent(ISFSEvent event) throws SFSException\r\n    {\r\n        User theUser = (User) event.getParameter(SFSEventParam.USER);\r\n\r\n        \/\/ dbid is a hidden UserVariable, available only server side\r\n        UserVariable uv_dbId = new SFSUserVariable(&quot;dbid&quot;, theUser.getSession().getProperty(DBLogin.DATABASE_ID));\r\n        uv_dbId.setHidden(true);\r\n\r\n        \/\/ The avatar UserVariable is a regular UserVariable\r\n        UserVariable uv_avatar = new SFSUserVariable(&quot;avatar&quot;, &quot;avatar_&quot; + theUser.getName() + &quot;.jpg&quot;);\r\n\r\n        \/\/ Set the variables\r\n        List&lt;UserVariable&gt; vars = Arrays.asList(uv_dbId, uv_avatar);\r\n        getApi().setUserVariables(theUser, vars);\r\n\r\n        \/\/ Join the user\r\n        Room lobby = getParentExtension().getParentZone().getRoomByName(&quot;The Lobby&quot;);\r\n\r\n        if (lobby == null)\r\n            throw new SFSException(&quot;The Lobby Room was not found! Make sure a Room called 'The Lobby' exists in the Zone to make this example work correctly.&quot;);\r\n\r\n        getApi().joinRoom(theUser, lobby);\r\n    }\r\n}\r\n<\/pre>\n<div class=\"dottedYellowBox\" style=\"text-align: center;\"><strong><a href=\"http:\/\/smartfoxserver.com\/download\/get\/214\">DOWNLOAD the source files for this recipe<\/a><\/strong><\/div>\n","protected":false},"excerpt":{"rendered":"<p>This recipe discusses the common scenario in which you need to validate the client credentials against the user profiles stored in a database. We will also show how to execute more custom logic right after the login, for example setting User Variables and joining a Room from server-side.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[5],"tags":[21,31,7],"_links":{"self":[{"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/posts\/642"}],"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=642"}],"version-history":[{"count":4,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/posts\/642\/revisions"}],"predecessor-version":[{"id":646,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/posts\/642\/revisions\/646"}],"wp:attachment":[{"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/media?parent=642"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/categories?post=642"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/smartfoxserver.com\/blog\/wp-json\/wp\/v2\/tags?post=642"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}