How should I implement 'Token Based Authentication' to a set of web APIs in a secure way using PHP and MySQL (without using OAuth)?

10,351

Based on our discussion, you could do something akin to OAuth2.0. I would recommend implementing the full spec, but since it is your application, you could make changes.

Here is a graph from RFC 6750

+--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|  (Slim API)   |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+

In slim, you could have as few as three endpoints:

POST username/password:

/oauth/v1/authenticate/

returns { token: foo }

GET where {token} is your unique token

/oauth/v1/token/{token}

returns { username: joe, permissions['page:admin','users:full'], expires: 123456}

DELETE pass the {token}

/oauth/v1/token/revoke

replies with 200 OK and an empty body.

Now, how it works:

  • When they auth, just return a JSON object with your token, which the client stores in something like a cookie.
  • The client passes this to your resource server in a header as identified in the RFC section 2.1 (which looks up the user permissions in a database/nosql/whatever):

GET /resource HTTP/1.1

 Host: server.example.com

 Authorization: Bearer mF_9.B5f-4.1JqM

Your resource server contacts the Slim API on the back-end to determine your permissions. The server then decides what you're allowed to see.

If you don't like sending it as a header, see section 2.2, which describes how to send it in the body, or section 2.3 which sends it as a URI query.

These obviously do not need to be different servers. You can implement it how you wish.

Share:
10,351
PHPLover
Author by

PHPLover

Updated on July 12, 2022

Comments

  • PHPLover
    PHPLover almost 2 years

    I've developed few web APIs in PHP using Slim framework which are used by mobile apps(iOS and Android) to process their requests and get the required data.

    Eventually, in every API I'm sending the requests received from mobile app to the respective function present in a code base of my website. Then the respective function accepts the request and request parameters, process the request and returns the required data. Then the API returns the data to the mobile app in JSON format. This is the current work flow.

    Now, I want to make the availability of website resources (i.e. functions from website's code base and data) subject to user authentication. In short, I want to implement 'Token Based Authentication' scheme in this scenario.

    Following should be the flow after I implement 'Token Based Authentication':

    1. When user first time logs in to the system by sending user name and password in an request to the login API, one security token will get generate upon successful verification of the user name and password combination for that particular user. This security token will also get stored into some MySQL database table along with the user name/password/some hashed value to identify the user in further processing. If the verification fails the security token should not get generate and the user should also not log in.

    2. Upon successful login, the generated security token will be sent back to the user in success response of login API. Now until the user is logged in with each subsequent request this token will be sent to the concerned API and ultimately it will be sent to the authentication function for the authentication of it's validity.

    3. If with any request an invalid token is sent then the "Please login first " message should be sent in response and the website resource should not get access.

    4. Once user logs out this security token entry should be deleted from the database or whatever appropriate action should be taken on it.

    Since, I'm working on 'Token Based Authentication' for the first time in my career I might be wrong with my above approach. If I'm doing anything wrong please correct my mistake/s.

    I found following links but those I didn't find much useful since they are lacking working code example with step-by-step description :

    PHP HMAC Restful API that uses Phalcon Micro framework

    PEAR package for JWT

    If you could provide me the entire code which consists the database table creation, connectivity between PHP and MySQL, then creation of security token, checking the validity of security token for the current logged in user, etc. it would be really great for me.

    Also, if you could give the working code example for any of the above two (or both) options as your answer to this question that would also be very great. If you have any other option apart from the two I provided with complete working code example you are welcome with your answer.

    N.B.:- Please don't suggest me to use OAuth authentication process.

    Following is the code I tried on my own but I don't know whether it's right or wrong. Is my approach correct or wrong?

    To create a token I use this function which takes as parameters, the user's data

    define('SECRET_KEY', "fakesecretkey");
    
    function createToken($data)
    {
        /* Create a part of token using secretKey and other stuff */
        $tokenGeneric = SECRET_KEY.$_SERVER["SERVER_NAME"]; // It can be 'stronger' of course
    
        /* Encoding token */
        $token = hash('sha256', $tokenGeneric.$data);
    
        return array('token' => $token, 'userData' => $data);
    }
    

    So a user can authentified himself and receive an array which contains a token (genericPart + his data, encoded), and hisData not encoded :

    function auth($login, $password)
    {
        // we check user. For instance, it's ok, and we get his ID and his role.
        $userID = 1;
        $userRole = "admin";
    
        // Concatenating data with TIME
        $data = time()."_".$userID."-".$userRole;
        $token = createToken($data);
        echo json_encode($token);
    }
    

    Then the user can send me his token + his un-encoded data in order to check :

    define('VALIDITY_TIME', 3600);
    
    function checkToken($receivedToken, $receivedData)
    {
        /* Recreate the generic part of token using secretKey and other stuff */
        $tokenGeneric = SECRET_KEY.$_SERVER["SERVER_NAME"];
    
        // We create a token which should match
        $token = hash('sha256', $tokenGeneric.$receivedData);   
    
        // We check if token is ok !
        if ($receivedToken != $token)
        {
            echo 'wrong Token !';
            return false;
        }
    
        list($tokenDate, $userData) = explode("_", $receivedData);
        // here we compare tokenDate with current time using VALIDITY_TIME to check if the token is expired
        // if token expired we return false
    
        // otherwise it's ok and we return a new token
        return createToken(time()."#".$userData);   
    }
    
    $check = checkToken($_GET['token'], $_GET['data']);
    if ($check !== false)
        echo json_encode(array("secureData" => "Oo")); // And we add the new token for the next request
    

    Am I right?