Reputation: 1323
I'm currently exploring the Parse platform by building some sample client-side apps with the Parse Javascript SDK. I've decided to abstract the majority of Parse-related logic away from the app and to a RESTful API running on a VPS. The purpose is to make the app as back-end agnostic as possible.
The RESTful API I've built is just an Express.js app that uses the Node.js Parse package.
My question is: When I want to make a request to my REST API, what is the best way to validate the user making the request?
Example: Consider an app where users can have friends:
So this would require the server-side Parse code to determine:
I've considered using the following approach:
router.get("/users/me/friends", function(req, res) {
var sessionToken = req.headers["sessionToken"];
Parse.User.become(sessionToken).then(function(user) {
/* Valid user and session: find and return the users friends */
}, function(error) {
/* Invalid: return an error status and the error */
});
});
Is this a valid way to handle this situation? Are there any problems that I should be aware of if I continue this way? Thanks!
Upvotes: 1
Views: 3615
Reputation: 96
I've been asking myself this same question and the solution I've come up with is very similar to what you are explaining. I'm developing an android app that uses Parse for it's User & Session pieces only, everything else will either be MySQL, Redis or whichever other datasources I plan to wrap in the future. I've gone ahead and developed my own Rest API on a VPS using PHP and Slim Framework. I've utilized the authentication piece in Parse by adding an ApiKey column in the users table that I generate myself and then also utilize the SessionToken that is already created and managed for each user. When the user makes authenticated calls to my web service, they provide both the api key and session token in every request header and when my server receives the request it makes sure that the apiKey + sessionToken pair is valid by verifying through Parse on it's end. The sessionToken is recreated every time the user logs back in, so this keeps it secure to an extent. So currently I'm using both the Parse Android SDK client-side and Parse PHP SDK server-side. I've been able to make create user, login user and list users endpoints regarding parse. Your mention of the 'become' ability really helped me with the issue I had with the user sending the session token to the server and trying to validate it since only the current user can view their own session info. I couldn't find any mention of it in the PHP docs, but apparently it's available and you just do something like the below. If you have any thoughts or input that you think could make this better, I'm open to ideas.
DBHandlerParse Class Excerpt:
/**
* Validating user api key
* If the api key is there in db, it is a valid key
* @param String $api_key user api key
* @return boolean
*/
public function isValidApiKey($api_key) {
$query = ParseUser::query();
$query->equalTo("apiKey",$api_key);
$results = $query->find();
if(count($results) > 0){
return true;
} else{
return false;
}
}
/**
* Validating user session token
* If the session token matches the api key user, it is a valid token
* @param String $session_token user session token
* @param String $api_key user api key
* @return boolean
*/
public function isValidSessionToken($session_token, $api_key) {
// Using already validated $api_key, obtain corresponding user object
$query = ParseUser::query();
$query->equalTo("apiKey",$api_key);
$results = $query->find();
if(count($results) > 0){
$userObj = $results[0];
} else{
return FALSE;
}
try{
// Become user that has this session token
// Only way to query back the user that they are
// If no user is found with this token, parse error
$thisUser = ParseUser::become($session_token);
$query = ParseSession::query();
$query->equalTo("user", $userObj);
$results = $query->find();
if(count($results) > 0){
return TRUE;
} else{
return FALSE;
}
} catch (Parse\ParseException $error){
return FALSE;
}
}
Authentication Middleware:
/**
* Adding Middle Layer to authenticate every request
* Checking if the request has valid api key in the 'Authorization' & 'Token' header
*/
function authenticate(\Slim\Route $route) {
// Getting request headers
$headers = apache_request_headers();
$response = array();
$app = \Slim\Slim::getInstance();
// Verifying Authorization Header
if (isset($headers['Authorization']) && isset($headers['Token'])) {
$db = new DbHandlerParse();
// get the api key
$api_key = $headers['Authorization'];
// get the session token
$session_token = $headers['Token'];
// validating api key
if (!$db->isValidApiKey($api_key)) {
// api key is not present in users table
$response["result"] = "error";
$response["message"] = "Access Denied. Invalid Api key";
echoRespnse(401, $response);
$app->stop();
} else if(!$db->isValidSessionToken($session_token, $api_key)) {
// session token does not match api key or is just invalid
$response["result"] = "error";
$response["message"] = "Access Denied. Invalid Token";
echoRespnse(401, $response);
$app->stop();
} else {
global $user_id;
// get user primary key id
$userID = $db->getUserId($api_key);
if (NULL != $userID)
$user_id = $userID;
}
} else if(!isset($headers['Authorization'])){
// api key is missing in header
$response["result"] = "error";
$response["message"] = "Api key is misssing";
echoRespnse(400, $response);
$app->stop();
} else {
// token is missing in header
$response["result"] = "error";
$response["message"] = "Token is misssing";
echoRespnse(400, $response);
$app->stop();
}
}
Example Route:
/**
* Users List
* url - /list
* method - GET
*/
$app->get('/users/list', 'authenticate', function() use ($app) {
$response = array();
$db = new DbHandlerParse();
$results = $db->getUserList();
$records = array();
//echo "Successfully retrieved " . count($results) . " scores.<br><br>";
// Do something with the returned ParseObject values
for ($i = 0; $i < count($results); $i++) {
$object = $results[$i];
$record = array();
$records[$i]['userId'] = $object->getObjectId();
$records[$i]['firstName'] = $object->get('firstName');
$records[$i]['lastName'] = $object->get('lastName');
$records[$i]['username'] = $object->get('username');
$records[$i]['email'] = $object->get('email');
//echo $object->getObjectId() . ' - ' . $object->get('username') . '<br>';
}
// check for records returned
if ($records) {
$response["result"] = "success";
$response['message'] = count($records)." users found.";
$response['items'] = $records;
} else {
// no records found
$response['result'] = 'success';
$response['message'] = 'No Users Found';
}
echoRespnse(200, $response);
});
Upvotes: 2