Hey there! There is also a (very) expanded version of this tutorial series available that includes the Facebook login portion. As well as getting a convenient PDF, ePub and mobi version, you'll also get a premium video tutorial series covering the development from start to finish and a bunch of other goodies! Check it out here .
Welcome to Part 3 of my tutorial series where I walk you through how to create a Sencha Touch application with a email + facebook signup and authentication system. If you haven’t seen it yet, make sure to start at Part 1. Here’s the rest of the series:
Part 1: Setting up the Database and API
Part 2: Creating the Application Screens
Part 3: Creating the User Model and API
Part 4: Finishing off the Logic
In the first part of this series we set up a bit of a skeleton for our backend PHP API. In this part we’re going to finish implementing the API, by implementing all necessary functionality in our users.php file. At the end of this part we should have everything ready to register and log in a user on the server side, but we will still need to implement the client side functionality that will allow us to send data to our server (which we will get to in Part 4).
Creating the User Model
app/model/User.js
The first thing we are going to do is create a model to describe our user. If you’ve used models and proxies before you’re probably used to connecting the model to a store and defining the proxy there. In this case, we’re just going to define the proxy directly on the model. As usual, I’ll post the code below and talk through it after:
Ext.define('SenchaLogin.model.User', {
extend: 'Ext.data.Model',
requires: ['Ext.data.identifier.Uuid'],
config: {
identifier: {
type: 'uuid',
},
fields: [
{ name: 'userId', type: 'int' },
{ name: 'email', type: 'string' },
{ name: 'password', type: 'string' },
{ name: 'session', type: 'string' },
],
validations: [
{
type: 'email',
field: 'email',
message: 'Please enter a valid email address',
},
],
proxy: {
type: 'ajax',
api: {
create:
'https://www.joshmorony.com/demos/SenchaLogin/api/users.php?action=createuser',
read: 'https://www.joshmorony.com/demos/SenchaLogin/api/users.php?action=readuser',
update:
'https://www.joshmorony.com/demos/SenchaLogin/api/users.php?action=updateuser',
destroy:
'https://www.joshmorony.com/demos/SenchaLogin/api/users.php?action=destroyuser',
},
reader: {
rootProperty: 'users',
},
},
},
});
We’re adding a uuid identifier to the model. All records in Sencha Touch require a unique id – we can generate this ourselves but by including the Ext.data.identifier.Uuid class the id generation is handled automatically. It’s worth noting that the userId field I’ve added has nothing to do with this and is not required – this field will be used to store the id of the user pulled in from the MySQL database.
The four fields we want (and you can certainly add more such as name, location, phone number etc.) are:
- userId – to identify the user in the database
- email + password – for authentication
- session – to store a session key
There is also a validation added for the email field that requires a valid email string to be supplied. Later, when we tell Sencha Touch to validate this model it will check to make sure the supplied email address is OK (but we will need to verify it doesn’t already exist in our database ourselves).
Finally, we define the proxy. The create, read, update and destroy methods are tied to a specific URL. In this case all requests are being directed to our users.php file and then we are able to direct what happens within that file by using the action GET parameter.
Essentially what this does is combine Sencha Touch functions with our server side backend – so whenever a record is saved it will invoke the create URL, when it is loaded the read URL, updated the update URL and when it is removed the destroy URL. This allows us to make the necessary changes to the database to reflect what has happened in the application. You will notice all of these URLs point to ‘https’. Although for testing / learning / development it’s OK to have these URLs pointing to a server without an SSL certificate (that is pointing them to http instead of https), it’s important that in a production environment user credentials are sent securely.
We’ve also defined the reader here with a rootProperty of ’users’ – we’re basically saying here that we’re going to pass a JSON string where the root node of the data we’re interested in reading will be called ‘users’ (more on that later).
One more thing…
app.js
Don’t forget to add your newly created model to your app.js file by adding the following line:
models: [
'User'
],
Continuing the API
Now we’re going to add in a switch statement that will perform various actions based on whichever URL is being invoked by the proxy. We will also be adding a couple of other cases to the switch statement to handle manual calls to the server which we will be adding to the application later (likely in the next part).
There’s quite a bit of code here so rather than pasting the code and explaining it after I’ll have the majority of the code commented. I’ll post each case individually and then have it all compiled into one big dump at the end.
Set up
users.php
In the first part of the series we set up a very basic skeleton, now we’re going to expand it to this:
<?php
$mysqli = new mysqli("localhost", "db_user", "your_password", "your_db");
class User {
function __construct($id, $email, $session){
$this->id = $id;
$this->email = $email;
$this->session = $session;
}
}
$action = $_GET["action"];
$result = "{'success':false}";
switch($action){
//cases go in here
}
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type,x-prototype-version,x-requested-with');
echo($result);
?>
You will notice the addition of the ’User’ class. This allows us to create a User object to JSON encode and send back to our application. I’ve left out the password field as there is no need for us to send the password back to the user. We’ve also added the switch statement for the action GET variable that is sent via the URL. In the rest of this post I will be describing the various cases that will be added to this switch statement.
Check Session
users.php
case "checksession":
//Check if user exists by matching their email address and session key
$query = "SELECT * FROM users WHERE email = '".$mysqli->real_escape_string($_POST['email'])."' AND session = '".$mysqli->real_escape_string($_POST['sessionKey'])."'";
$dbresult = $mysqli->query($query);
$details = $dbresult->fetch_assoc();
//A match was found, so let's authenticate the user
if($dbresult->num_rows > 0){
//Generate a new session key
$key = md5(microtime().rand());
//Update the session key in the database
$query = "UPDATE users SET session = '".$key."' WHERE email = '".$mysqli->real_escape_string($_POST['email'])."' AND session = '".$_POST['sessionKey']."'";
if($dbresult = $mysqli->query($query))
{
//Key successfully updated, indicate success and send the key back to the application
//This is a JSON string.
$result = "{'success':true, 'session':'".$key."'}";
}
else
{
//Key was not updated, pass back the old one (this shouldn't happen)
$result = "{'success':true, 'session':'".$_POST['sessionKey']."'}";
}
}
else
{
//No match was found, tell the application a failure occured
$result = "{'success':false, 'error':'Session key did not match'}";
}
break;
The purpose of the checksession case is to allow users to be authenticated without always having to enter in their password. On their first visit they will be asked to login in, which will grant them a session key. Upon subsequent visits they will be logged in automatically.
Login
users.php
case "login":
//Check if user exists for this email / password combo
//Passwords are not stored in the database in plain text, we are using the 'sha1' function to hash them
$query = "SELECT * FROM users WHERE email = '".$mysqli->real_escape_string($_POST['email'])."' AND password = '".sha1($_POST['password'])."'";
$dbresult = $mysqli->query($query);
$details = $dbresult->fetch_assoc();
if($dbresult->num_rows > 0){
//A match was found. Indicate success and send the details of the user back to the application
$result = "{'success':true, 'details':[" . json_encode(new user($details['id'], $details['email'], $details['session'])) . "]}";
break;
}
else
{
//No match was found, indicate failure
$result = "{'success':false, 'error':'Sorry, your credentials were incorrect.'}";
}
break;
The login case is pretty obvious. We’re checking for a match for the supplied email / password combination in the database. If one is found we indicate success and if not we indicate that the credentials were incorrect.
Create User
users.php
case "createuser":
//Grab the data
$userJSON = file_get_contents('php://input');
$user = json_decode($userJSON);
//Check if email already exists
$query = "SELECT * FROM users WHERE email = '".$mysqli->real_escape_string($user->email)."'";
$dbresult = $mysqli->query($query);
//Return an error if the email address has already been used
if($dbresult->num_rows > 0){
$result = "{'success':false, 'error':'Sorry, that email address has already been used.'}";
break;
}
//Generate a new session key
$key = md5(microtime().rand());
//Hash the password supplied by the user
$hashedPassword = sha1($user->password);
//Insert the new user into the database
$query = "INSERT INTO users (email, password, session) VALUES( '".$mysqli->real_escape_string($user->email)."', '".$hashedPassword."', '".$key."' )";
$dbresult = $mysqli->query($query);
//Grab the id of the newly created user to pass back to the application
$id = $mysqli->insert_id;
//If successful pass back the users information, otherwise indicate failure
if($dbresult){
$result = "{'success':true, 'users':[" . json_encode(new user($id, $user->email, $key)) . "]}";
}
else
{
$result = "{'success':false}";
}
break;
This is an interesting one because we’re excepting more complex data. The previous cases will be invoked through manual calls with POST variables, but this is one of the cases tied to the proxy we defined in the Sencha Touch application. We are able to grab the data through file_get_contents and then decode that JSON string to create an object in PHP containing all the data we want to create our user.
That’s it for now!
You will notice I haven’t created a case for all of the URLs in our proxy. This is because we don’t really need them. At least in this application there will be no point where we delete the user or update the users details in the database. Perhaps if you are developing a more complex application you may wish to provide a forgot password facility that would then require the user to be updated in the database.
Stay tuned for the next part, and here’s the code in for the users.php file in full:
UPDATE: Part 4 is ready, check it out here.
<?php
$mysqli = new mysqli("localhost", "db_user", "your_password", "your_db");
class User {
function __construct($id, $email, $session){
$this->id = $id;
$this->email = $email;
$this->session = $session;
}
}
$action = $_GET["action"];
$result = "{'success':false}";
switch($action){
case "checksession":
//Check if user exists by matching their email address and session key
$query = "SELECT * FROM users WHERE email = '".$mysqli->real_escape_string($_POST['email'])."' AND session = '".$mysqli->real_escape_string($_POST['sessionKey'])."'";
$dbresult = $mysqli->query($query);
$details = $dbresult->fetch_assoc();
//A match was found, so let's authenticate the user
if($dbresult->num_rows > 0){
//Generate a new session key
$key = md5(microtime().rand());
//Update the session key in the database
$query = "UPDATE users SET session = '".$key."' WHERE email = '".$mysqli->real_escape_string($_POST['email'])."' AND session = '".$_POST['sessionKey']."'";
if($dbresult = $mysqli->query($query))
{
//Key successfully updated, indicate success and send the key back to the application
//This is a JSON string.
$result = "{'success':true, 'session':'".$key."'}";
}
else
{
//Key was not updated, pass back the old one (this shouldn't happen)
$result = "{'success':true, 'session':'".$_POST['sessionKey']."'}";
}
}
else
{
//No match was found, tell the application a failure occured
$result = "{'success':false, 'error':'Session key did not match'}";
}
break;
case "login":
//Check if user exists for this email / password combo
//Passwords are not stored in the database in plain text, we are using the 'sha1' function to hash them
$query = "SELECT * FROM users WHERE email = '".$mysqli->real_escape_string($_POST['email'])."' AND password = '".sha1($_POST['password'])."'";
$dbresult = $mysqli->query($query);
$details = $dbresult->fetch_assoc();
if($dbresult->num_rows > 0){
//A match was found. Indicate success and send the details of the user back to the application
$result = "{'success':true, 'details':[" . json_encode(new user($details['id'], $details['email'], $details['session'])) . "]}";
break;
}
else
{
//No match was found, indicate failure
$result = "{'success':false, 'error':'Sorry, your credentials were incorrect.'}";
}
break;
case "createuser":
//Grab the data
$userJSON = file_get_contents('php://input');
$user = json_decode($userJSON);
//Check if email already exists
$query = "SELECT * FROM users WHERE email = '".$mysqli->real_escape_string($user->email)."'";
$dbresult = $mysqli->query($query);
//Return an error if the email address has already been used
if($dbresult->num_rows > 0){
$result = "{'success':false, 'error':'Sorry, that email address has already been used.'}";
break;
}
//Generate a new session key
$key = md5(microtime().rand());
//Hash the password supplied by the user
$hashedPassword = sha1($user->password);
//Insert the new user into the database
$query = "INSERT INTO users (email, password, session) VALUES( '".$mysqli->real_escape_string($user->email)."', '".$hashedPassword."', '".$key."' )";
$dbresult = $mysqli->query($query);
//Grab the id of the newly created user to pass back to the application
$id = $mysqli->insert_id;
//If successful pass back the users information, otherwise indicate failure
if($dbresult){
$result = "{'success':true, 'users':[" . json_encode(new user($id, $user->email, $key)) . "]}";
}
else
{
$result = "{'success':false}";
}
break;
}
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type,x-prototype-version,x-requested-with');
echo($result);
?>