We are moving our forum in GitHub Discussions. For questions about Phalcon v3/v4 you can visit here and for Phalcon v5 here.

Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

Adding minimalist token authentication to the REST Tutorial

Hello

I have looked at the HMAC authentication (https://phalcon-rest.redound.org/) users suggested here but it looks too excessive for me and it does not use Micro framework. I just want to add a very simple and minimaist authentication to the REST example of the phalcon website.

The only thing I need is when a call arrives (to specific API functions that require auth) I can somehow find which user is behind the call.

So I need to:

1- Intercept calls and "if they require authentication" but the request does not provide a valid token (i.e. one that belongs to a user) I can send back a json error message.

2- Inside api functions I can call a getUser function (looks which user is the owner of current token) and get the user info. Even if I can get just the token, I can do the rest.

That's it.

So, is there a minimalist example that has implemented just that?

Thanks.



93.7k
Accepted
answer

Hey, I think I have just the thing you need. One of my clients has an API only for internal use. Everything is only for local network, so I needed to know which user did the request, nothing more...

For this I used Authorization: Bearer UNIQUE_TOKEN header which is sent with every API request.

Sample code that does the job:

BaseController - this is the controller that all other API Controllers extend.


public function beforeExecuteRoute()
{
    // Route names that do not need authorization
    $authorizeExceptions = [
        'api-documentation'
    ];
    if (!in_array($this->router->getMatchedRoute()->getName(), $authorizeExceptions)) {
        // Authorize
        $result = $this->authorize();
        if (is_null($result)) {
            $this->_response['messages'] = 'Please authorize with valid API token!';
            $this->_response['statusCode'] = 401;
            $this->afterExecuteRoute();
            die();
        }
    }

    // We accept only application/json content in POST and PUT methods
    if (in_array($this->request->getMethod(), ['POST', 'PUT']) AND $this->request->getHeader('Content-Type') != 'application/json') {
        $this->_response['messages'] = 'Only application/json is accepted for Content-Type in POST requests.';
        $this->_response['statusCode'] = 400;
        $this->afterExecuteRoute();
        die();
    }
}

// Return the API response.
public function afterExecuteRoute()
{
    // Status code & Response header
    $this->_response['statusMessage'] = $this->_statusCodes[$this->_response['statusCode']];
    $this->response->setStatusCode($this->_response['statusCode'], $this->_statusCodes[$this->_response['statusCode']]);
    $this->response->setHeader('Access-Control-Allow-Origin', '*');
    $this->response->setHeader('X-Content-Type-Options', 'nosniff');
    $this->response->setHeader('X-Frame-Options', 'deny');
    $this->response->setHeader('Content-Security-Policy', 'default-src \'none\''); 

    // Set content
    $this->response->setContentType('application/json', 'UTF-8'); 
    $this->response->setJsonContent($this->_response, JSON_UNESCAPED_UNICODE); 

    // Log
    if (!is_null($this->user) AND $this->user->id != 1) {
        $request = $this->request->get();
        $request['accept'] = $this->request->getHeader('Accept');
        $this->db->insertAsDict('api_access_logs', [
            'api_user_id' => $this->user->id,
            'endpoint' => $this->request->getMethod() .' '. getCurrentUrl(false, false),
            'request' => json_encode($request, JSON_UNESCAPED_UNICODE),
            'response' => json_encode($this->_response, JSON_UNESCAPED_UNICODE),
        ]);
    }
    return $this->response->send();
}

// Check if valid Token is given
private function authorize()
{
    $this->user = null;
    $authorizationHeader = $this->request->getHeader('Authorization');
    if ($authorizationHeader AND preg_match('/Bearer\s(\S+)/', $authorizationHeader, $matches)) {
        $tokenParts = explode('|', \Helpers\Common::decodeString($matches[1]));
        // For now token has 3 parts. Update here if you modify token
        // IMPORTANT: Here you can make your custom logic to check for valid token
        if (count($tokenParts) === 3) {
            $this->user = (object) [
                'id' => $tokenParts[0],
                'level' => $tokenParts[1],
            ];
        }
    }
    return $this->user;
}

In the authorize() method I'm encoding/decoding the token with \Phalcon\Crypt library.


class Common
{
    private static $cryptKey = 'i$1^&/:%[email protected]!R1Q<@{([email protected]*!<7u|R2~0';
    public static function encodeString($string)
    {
        return (new \Phalcon\Crypt)->encryptBase64($string, self::$cryptKey, true);
    }
    public static function decodeString($string)
    {
        return (new \Phalcon\Crypt)->decryptBase64($string, self::$cryptKey, true);
    }
    ...
}


5.5k

Nikolay, Thank you very much.

All I should do now is to convert it to a middleware (since I am using Micro Framework).

May I ask what might go wrong if I use that for an internet api? Is there a risk (if I use https with all calls)?

Thanks again for generus sharing of your code.

Well all depends on what you want to achieve.

I found an article where its explained better then i ever will :)

https://zapier.com/engineering/apikey-oauth-jwt/



5.5k

Just converted the code provided by Nicolay to a very minimal middleware:

<?php
use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Router;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;
use Phalcon\Mvc\Micro\MiddlewareInterface;

class AuthMiddleware implements MiddlewareInterface
{
    public function beforeExecuteRoute(Event $event, Micro $application)
    {
        $authorizeExceptions = [
            'doc'
        ];
        if (!in_array($application->router->getMatchedRoute()->getName(), $authorizeExceptions)) {
            $result = $this->authorize($application);
            if (is_null($result)) {
                $application->response->setStatusCode(401,'Please authorize with valid API token!');
                $application->response->setContent('Please authorize with valid API token!');
                $application->response->send();
                die();
                //return false;
            }
        }

        if (in_array($application->request->getMethod(), ['POST', 'PUT']) AND $application->request->getHeader('Content-Type') != 'application/json') {
            $application->response->setStatusCode(400,'Only application/json is accepted for Content-Type in POST requests.');
            $application->response->send();
            die();
            //return false;
        }

        return true;
    }

    private function authorize(Micro $application)
    {
        $application->token = null;
        $authorizationHeader = $application->request->getHeader('api-token');

        if (strlen($authorizationHeader)>5) {  //check token validity and find from database what user has the token
            $application->token = $authorizationHeader;
            //$application->userid = ?;
        }

        return $application->token;
    }    

    public function call(Micro $application)
    {
        return true;
    }    
}

$app = new Micro();

$eventsManager = new Manager();
$eventsManager->attach('micro', new AuthMiddleware());
$app->before(new AuthMiddleware());
$app->after(new AuthMiddleware());
$app->setEventsManager($eventsManager);

$app->get(
    '/',
    function () use ($app){
        echo '<h1>Welcome!</h1> token: '. $app->token ."<br>";
    }
);

$app->get(
    '/doc',
    function () {
        echo '<h1>api-documentation!</h1>';
    }
)->setName('doc');

$app->get(
    '/profile/{username}',
    function ($username) use ($app) {
        $content = "<h1>This is profile of: {$username}!</h1>";
        $app->response->setContent($content);
        $app->response->send();
    }
);

$app->handle();


43.8k

thanks for sharing.