This sub-section details the functions and facilities within Cliqon that relate to our RESTful API and the services. This is a work in progress and will change relatively regularly. This is in part required because the principles and implementations are changing rapidly. For example, the JSON Web Token protocol is in a period of transition and the arrangements described in this sub-section will be deprecated at the end of 2020.


We start with a reprint of an article from Sitepoint on the implementation of JWT, which is based on the JWT Library from Firebase. This is the second of the two libraries that we have implemented in Apiservices.Php.

JSON Web Tokens

Since October 2010, there have been several proposals to use JSON based tokens. JWT or JSON Web Token was proposed on December 2010, having the following characteristics:


  •    Intended for space constrained environments, such as HTTP Authorization headers or query string parameters.
  •    Data to be transmitted in Javascript Object Notation format (JSON)
  •    The data has to be the payload of a JSON Web Signature (JWS)
  •    Represented using Base64 URL encoding


The JSON Web Signature is a cryptographic mechanism designed to secure data with a digital signature unique to the contents of the token in such a way that we are able to determine whether the data of the token has been tampered with or not.


The use of JWTs has many advantages over a single API key:


  •    API keys are just random strings, while JWTs contain information and metadata that can describe user identity, authorization data and the validity of the token within a time frame or domain.
  •    JWTs do not require a centralized issuing or revoking authority.
  •    OAUTH2 compatible.
  •    JWT data can be inspected.
  •    JWTs have expiration controls.


On May 19th 2015, JWT became a published IETF RFC 7519.

The design of a JWT

A JWT would look like the following:


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E


It would appear that the string is just random characters concatenated together, and not very different from an API key. However, if you look closely, there are actually 3 strings, separated by a dot character. The first and second strings are Base64 URL encoded JSON strings, so if we decode those, we will have the following results:


{

 "alg": "HS256",

 "typ": "JWT"

}


{

 "iat": 1416929109,

 "jti": "aa7f8d0a95c",

 "scopes": [

   "repo",

   "public_repo"

 ]

}


The first string is the JWS header, which states which cryptographic algorithm was used to generate the signature and the payload type. The second string is the payload, and passes along some standard fields, any data you wish to send within the token. The third string is the cryptographic signature, and will decode to binary data.


What is interesting about the signature is that the cryptographic algorithm requires a secret key, a string that only the issuer application has to know and should never be disclosed in any way. This way when the application receives a token, it can verify the signature against the contents of the token using said secret key. If the signature verification fails, we can know for sure that the data within the token has been tampered with and should be discarded. You can take a look at jwt.io where you can play around with encoding and decoding JWTs.

JWT in practice

So how do we apply this to a PHP app? Let’s say we have a login mechanism that currently uses session cookies to store information about a user’s login state within the application. Please note that JWT was not designed to substitute session cookies. However, for this example, we will have a couple of services: one that generates a JWT based on the provided username and password, and another that will fetch a protected resource provided we supply a valid JWT.


You will design a Login form for the desktop or the device. The contents of the login form should be Posted to the relative URL /api/{lcd}/apilogin/. The example image shows the ability to use OAuth2 and logins by Facebook and Google. This is possible, but requires the Bshaffer OAUTH2 library that we have modified and is available as a downloadable module.


Once we are signed in, we will be able to retrieve a protected resource from the application. In the example below we have done some basic conversion of the example code to be Cliqon specific but not everything that would be needed. I note that in the example, the JWT is sent in the Header or the Request, whereas we send the JWT in the REQUEST. Therefore the example validates the JWT with PHP code that for use in Cliqon, needs to be included in the API Controller.


Now, let us assume that the login form submits the data to the Cliqon API service via AJAX, where the credentials are validated against a database, and after determining that the credentials are valid, we have to build our token. Let us build it as an array first:


global $clq;


if ($credentialsAreValid) {


   $tokenId    = base64_encode(mcrypt_create_iv(32));

   $issuedAt   = time();

   $notBefore  = $issuedAt + 10;             //Adding 10 seconds

   $expire     = $notBefore + 60;            // Adding 60 seconds

   $serverName = $clq->get('cfg')['database]['server'];    // Retrieve the server name from config file

   

   /*

    * Create the token as an array

    */

   $data = [

       'iat'  => $issuedAt,         // Issued at: time when the token was generated

       'jti'  => $tokenId,          // Json Token Id: an unique identifier for the token

       'iss'  => $serverName,       // Issuer

       'nbf'  => $notBefore,        // Not before

       'exp'  => $expire,           // Expire

       'data' => [                  // Data related to the signer user

           'userId'   => $rs['id'], // userid from the users table

           'userName' => $username, // User name

       ]

   ];

}


Please notice that you can define the data structure however you want, there are however some reserved claims, such as the ones used above:


  •    iat – timestamp of token issuing.
  •    jti – A unique string, could be used to validate a token, but goes against not having a centralized issuer authority.
  •    iss – A string containing the name or identifier of the issuer application. Can be a domain name and can be used to discard tokens from other applications.
  •    nbf – Timestamp of when the token should start being considered valid. Should be equal to or greater than iat. In this case, the token will begin to be valid       10 seconds
  •    after being issued.
  •    exp – Timestamp of when the token should cease to be valid. Should be greater than iat and nbf. In this case, the token will expire 60 seconds after being issued.


Those claims are not required, but will help you determine the validity of a token (more on this later). Our application’s payload comes inside the data claim, where we are storing the userId and userName values. Since a JWT can be inspected client side, please remember not to include any sensitive information in it.


Transforming this array into a JWT is super easy:


   /*

    * Extract the key, which is coming from the config file.

    * Best suggestion is the key to be a binary string and

    * store it in encoded in a config file.

    *

    * Can be generated with base64_encode(openssl_random_pseudo_bytes(64));

    * keep it secure! You'll need the exact key to verify the

    * token later.

    */

   $secretKey = base64_decode($clq->get('cfg')['site']['secret']);

   

   /*

    * Encode the array to a JWT string.

    * Second parameter is the key to encode the token.

    *

    * The output string can be validated at http://jwt.io/

    */

   $jwt = API::encode(

       $data,      //Data to be encoded in the JWT

       $secretKey, // The signing key

       'HS512'     // Algorithm used to sign the token,

see https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-3

       );

       

   $unencodedArray = ['jwt' => $jwt];

   echo json_encode($unencodedArray);

       

API::encode() will take care of everything (transforming the array to JSON, producing the headers, signing the payload and encoding the final string). You will want to make your secret key a long, binary string, encode it in a config file and never disclose it. Having it directly in your code is a bad idea.


Now that the client has the token, you can store it using JS or whichever mechanism you like. Here is an example using jQuery:


$(function(){  

// Submit the login form via ajax

       $("#frmLogin").submit(function(e){

               e.preventDefault();

               $.post('auth/token', $("#frmLogin").serialize(), function(data){

                       cfg.jwt = data.jwt;

               }).fail(function(){

                       Cliq.error('error');

               });

       });

});


Now let us retrieve a resource that is protected by our JWT mechanism.


When clicking on the “Get resource >>” button, if everything is all right, you should see an image in the grey area. Let us use an AJAX call to send the request to the resource service:


$("#btnGetResource").click(function(e){

       e.preventDefault();

       $.ajax({

               url: '/api/+jlcd+'/getdata/',

               data: frmData,

               beforeSend: function(request){

                       request.setRequestHeader('Authorization', 'Bearer ' + store.JWT);

               },

               type: 'GET',

               success: function(response) {

                       if(typeof response == 'object') {

                       // Test NotOK - value already exists

                       var match = /NotOk/.test(response.flag);

                       if(!match == true) {

                                       // Decode and show the returned data nicely.

                               } else { Cliq.error(JSON.stringify(response)); };

                       } else { Cliq.error(response.msg); }

               },

               error: function() {

                       alert('error');

               }

       });

});


Please notice the beforeSend option. We are telling jQuery that before every request is made through this call, we need to set the Authorization header with the contents of the JWT in the format of Bearer [JWT]. So when we click the button, the following request is made:


Please notice the beforeSend option. We are telling jQuery that before every request is made through this call, we need to set the Authorization header with the contents of the JWT in the format of Bearer [JWT]. So when we click the button, the following request is made:


GET /resource.php HTTP/1.1

Host: yourhost.com

Connection: keep-alive

Accept: */*

X-Requested-With: XMLHttpRequest

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0MjU1ODg4MjEsImp0aSI6IjU0ZjhjMjU1NWQyMjMiLCJpc3MiOiJzcC1qd3Qtc2ltcGxlLXRlY25vbTFrMy5jOS5pbyIsIm5iZiI6MTQyNTU4ODgyMSwiZXhwIjoxNDI1NTkyNDIxLCJkYXRhIjp7InVzZXJJZCI6IjEiLCJ1c2VyTmFtZSI6ImFkbWluIn19.HVYBe9xvPD8qt0wh7rXI8bmRJsQavJ8Qs29yfVbY-A0


Now the protected resource should be displayed


This is how we validate the token in the resource service.


$authHeader = $request->getHeader('authorization');


// Look for the 'authorization' header

if ($authHeader) {

       

       // Extract the jwt from the Bearer

       list($jwt) = sscanf( $authHeader->toString(), 'Authorization: Bearer %s');


       if ($jwt) {

               try {

                       global $clq;

                       // decode the jwt using the key from config

                       $secretKey = base64_decode($clq->get('cfg')['site']['secret']);

                       $token = API::decode($jwt, $secretKey, array('HS512'));

                       $asset = base64_encode(file_get_contents('http://lorempixel.com/200/300/cats/'));


                       // return protected asset

                       header('Content-type: application/json');

                       echo json_encode([

                               'img'    => $asset

                       ]);


               } catch (Exception $e) {

                       //  the token was not able to be decoded.

                       // this is likely because the signature was not able to be verified (tampered token)

                       header('HTTP/1.0 401 Unauthorized');

               }

       } else {

               // No token was able to be extracted from the authorization header

               header('HTTP/1.0 400 Bad Request');

       }

} else {

       // The request lacks the authorization token

       header('HTTP/1.0 400 Bad Request');

       echo 'Token not found in request';

}



$request = new Request();

if ($request->isGet()) { //Will only process HTTP GET requests.

       $authHeader = $request->getHeader('authorization');

       // ...


Now let’s find out if the authorization header has a JWT string in it:


// Look for the 'authorization' header

if ($authHeader) {

   // Extract the JWT from the Bearer

   list($jwt) = sscanf( $authHeader->toString(), 'Authorization: Bearer %s');

   // MORE CODE

}


This way the variable $jwt will have the contents of a potential JWT.


One alternative you might choose if you do not want to deal with HTTP Authorization headers, is to include the token in the request as a URL parameter:


GET /resource.php?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0MjU1ODg4MjEsImp0aSI6IjU0ZjhjMjU1NWQyMjMiLCJpc3MiOiJzcC1qd3Qtc2ltcGxlLXRlY25vbTFrMy5jOS5pbyIsIm5iZiI6MTQyNTU4ODgyMSwiZXhwIjoxNDI1NTkyNDIxLCJkYXRhIjp7InVzZXJJZCI6IjEiLCJ1c2VyTmFtZSI6ImFkbWluIn19.HVYBe9xvPD8qt0wh7rXI8bmRJsQavJ8Qs29yfVbY-A0 HTTP/1.1

Host: yourhost.com

Connection: keep-alive

Accept: */*

X-Requested-With: XMLHttpRequest


Let’s try to decode the JWT now. Remember the secret key we used earlier to generate the token? It is a vital part of the decoding process here:


$secretKey = base64_decode($clq>get('cfg')['site']['secret']);

// decode the JWT using the key from config

$token = API::decode($jwt, $secretKey, array('HS512'));


If the process to decode the JWT fails, it could be that:


  •    The number of segments provided did not match the standard 3 as described earlier.
  •    The header or the payload is not a valid JSON string
  •    The signature is invalid, which means the data was tampered with!
  •    The nbf claim is set in the JWT with a timestamp, when the current timestamp is less than that.
  •    The iat claim is set in the JWT with a timestamp, when the current timestamp is less than that.
  •    The exp claim is set in the JWT with a timestamp, when the current timestamp is more than that.


As you can see, JWT has a nice set of controls that will mark it as invalid, without the need to manually revoke it or check it against a list of valid tokens.


In case you were wondering about the JWT signature and tampered data, this is possible thanks to cryptographic Message authentication codes. In a nutshell, arbitrary data input along with a key will produce a unique ‘fingerprint’ of the data. This fingerprint alone cannot be reversed back to the data input and the slightest change to either the data input or the key will produce a totally different fingerprint.


At this point we can be sure that the JWT is valid. Additionally, you could check if the user in the token is still valid, if the issuer of the token (from the iss claim) is you, or if your token has embedded permission flags, then check those against the action the user is requesting to perform.


Finally, we request an image from lorempixel.com, base64 encode it and return it in a json response string:


$asset = base64_encode(file_get_contents('http://lorempixel.com/200/300/cats/'));


// return protected asset

header('Content-type: application/json');

echo json_encode([

   'img'    => $asset

]);

API Services

The Cliqon production system has no need to use API Services, this will be purely the area of Cliqon development reserved for contributors and partners. We shall be doing some work on our own account and will also develop demonstration services to harness the resources we have created and to help others with support.



Created with the Personal Edition of HelpNDoc: Full-featured Documentation generator