API Project - Part 9 - Securing Resources
At the moment, anyone can submit requests to our API and it will respond (if there is a matching route for the requested URL). We need to make sure that only authenticated users can get resources from the API.
We also need to make sure that only admins can make the following requets:
users/ (GET, POST, DELETE)
A standard user can make the following GET request ONLY IF the :id is matches their own ID (which are are storing in session variables).
users/:id (GET, PUT)
In other words, a standard user can only get their own user details. A standard user may also make PUT requests to users/:id only if the :id in the url matches their own ID.
In order to make these security checks, we'll add few methods to the Controller class, and they will be inherited by all of our controllers.
Add these methods to the Controller class, they will allow us to run security checks when requests are made to the server:
// Checks to see if the user making the request is an admin
function isAdmin(){
$admin_role_id = 2; // the role id (in the user_roles table) for admins
if(isset($_SESSION['user_role_id']) && $_SESSION['user_role_id'] == $admin_role_id){
return true;
}
return false;
}
// Checks to see if the user making the request has authenticated
function isLoggedIn(){
if(isset($_SESSION['authenticated']) && $_SESSION['authenticated'] == "yes"){
return true;
}
return false;
}
// Checks to see if the $userId param matches the id of the user making the request
function isOwner($userId){
if(isset($_SESSION['user_id']) && $_SESSION['user_id'] == $userId){
return true;
}
return false;
}
Once we start using these methods in our controllers, it gets to be tricky to test the app (you need to be sure that you are logged as the proper user in order to make certain requests). So I devised a crude mechanism that allows you to disable security checks. I defined a constant in the config file named SECURE_SERVER_RESOURCES Look in the config file and note that I set the constant to FALSE for the dev environment, and TRUE for the live environment.
We will want to change it to TRUE for both environments when we start testing our security checks.
Now, open the UserController class and add this at the beginning of the POST case in the handleUsers() method:
if(SECURE_SERVER_RESOURCES){
if(!$this->isAdmin()){
$this->sendStatusHeader(401, "Only admins can insert new users");
die();
}
}
The outer IF statement just checks to make sure that we have enabled the security checks. The inner IF statement will check to see if the user making the request is an admin. If not, it sends a 401 status header and terminates with die(). A status code of 401 tells the browser the it is not authorized to make the request.
Now let's test this by opening http://localhost/api/tests/SecurityTests.html in the browser. Do not login, instead press the POST User button and look in the console log and the network tab and observe that a status code of 401 has been sent in the response.
Now go ahead and log in as an admin, and then press the POST User button again. Hopefully you are now able to POST new users.
Now we'll go about securring the rest of the UserController. Add this to the top of the GET case in the handleUsers() method:
if(SECURE_SERVER_RESOURCES){
if(!$this->isAdmin()){
$this->sendStatusHeader(401, "Only admins can get all users");
die();
}
}
Now return to our test page and log out (press the Log Out button) and then press the Get All Users button. You should see another 401 status code from the server.
Now log in as an admin and try it again. The request should work now.
Add this to the GET case in the handleSingleUser() method:
if(SECURE_SERVER_RESOURCES){
if(!$this->isAdmin() && !$this->isOwner($id)){
$this->sendStatusHeader(401, "Only admins or owners can get a user (a user can get his/her own info)");
die();
}
}
Note that the inner IF statement is now checking the user id being requested, and the isOwner() method will return false if the $id parameter does not match the user id that is stored in session.
On the test page, log out and then press the Get User By ID button. The request should fail.
Login as an admin and then try it again.
Now log out and log back in as a user who is not an admin. You should only be able to get a user by id if you are logged in as that user.
Add this at the top of the PUT case in the handleSingleUser() method:
if(SECURE_SERVER_RESOURCES){
// note that admins can update any user
// but standard users can only update their 'own' info
if(!$this->isAdmin() && !$this->isOwner($id)){
$this->sendStatusHeader(401, "Only admins or owners can put a user (a user can put his/her own info)");
die();
}
}
//ISSUE: Users should not be able to change their own role or active status!
Now try updating a user while logged in as an admin, and as a standard user.
Note the issue! A standard user can send a request that changes his/her role to admin!!! This issue would have to be fixed before going live, hopefully we have time to fix it!
Finally, add this code to the top of the DELETE case:
if(SECURE_SERVER_RESOURCES){
if(!$this->isAdmin()){
$this->sendStatusHeader(401, "Only admins can delete a user");
die();
}
}
You will have to secure the resources in the controllers that you create for your final project.
Now that you have secured your resources, we can start working on going live with the project.