Authenticate usertoken before app run with Slim 3 - php

I am using Slim Framework 3 to make a small internal API to get fetch facebook data. There is about 30 specific users which have access to the API.
I want to authenticate a user by a user token send from the website, and that token is to be checked before the app is run.
The token on the user is set in the DB and when the user is requesting the API a token is send with a GET and if there is a match on the DB and the GET token, the user should be granted access to the API, otherwise the user should be forbidden to access.
I am using this to get facebook data:
$app->get('/fbdata/campaign/{campaign}/bankarea/{bankarea}/from/{from}/to/{to}/utoken/{utoken}', function(Request $request, Response $response) {
$bd = new BankAppData();
$getFb = new GetFacebookData();
$bankarea = $request->getAttribute('bankarea');
$campaign = $request->getAttribute('campaign');
$appid = $bd->BankData($bankarea)->appid;
$appsecret = $bd->BankData($bankarea)->appsecret;
$fbtoken = $bd->BankData($bankarea)->fbtoken;
$dateFrom = $request->getAttribute('from');
$dateTo = $request->getAttribute('to');
$getFb->FetchData($appid, $appsecret, $fbtoken, $campaign, $bankarea, "act_XXXX", $dateFrom, $dateTo);
});
This works just fine, but I want to use a AuthenticationHandler class for checking the utoken before the above is run.
I am adding it by using $app->add(new SNDB\AuthenticationHandler()); but I am unsure on how I can get the utoken from the URL in my AuthenticationHandler class.
Basically I want to do something like
function Authenticate() {
if($dbToken != $utoken) {
//No access - app will just stop doing anything else
} else {
//You have access - just continue what you was trying to do
}
}

You should take a look at the middleware concept from slim3.
Basically there are 2 options how to add middleware:
per anonymous function
$app->add(function ($request, $response, $next) {
$response->getBody()->write('BEFORE');
$response = $next($request, $response);
$response->getBody()->write('AFTER');
return $response;
});
per invokable class
class ExampleMiddleware
{
public function __invoke($request, $response, $next)
{
$response->getBody()->write('BEFORE');
$response = $next($request, $response);
$response->getBody()->write('AFTER');
return $response;
}
}
$app->add(new ExampleMiddleware);
There you have the PSR-7 request and can get your utoken from the url.

Related

does Slim framework have URL encoded annotation for POST method?

I'm developing a web RESTful API using slim framework of php.I want to know how do I add some annotation type thing on POST method so that it can behave as URL encoded method.Please help me in this regard.Advance thanks.
There is no pre-programmed way for this - there is no Slim or php method that will definitively check if your string is urlencoded. What you can do is implement Slim Middleware to your route.
<?php
$app = new \Slim\App();
$mw = function ($request, $response, $next) {
if ( urlencode(urldecode($data)) === $data){
$response = $next($request, $response);
} else {
$response = ... // throw error
}
return $response;
};
$app->get('/', function ($request, $response, $args) { // Your route
$response->getBody()->write(' Hello ');
return $response;
})->add($mw); // chained middleware
$app->run();
Discussion: Test if string is URL encoded in PHP
Middleware: https://www.slimframework.com/docs/v3/concepts/middleware.html
Since you're using Slim as the foundation to your API, the easiest way is to just build a GET route with the desired URL parameters defined:
$app->get('/users/filter/{param1}/{param2}/{param3}', function (Request $request, Response $response) {
// Route actions here
});
In your documentation, make sure you inform the consumers of this API that it is a GET endpoint, so that a POST body should not be made; rather, the parameters that you outline in the URL should be used to pass the client's data over to the API.
If you are intent on using a POST route with just URL parameters, then you could also force a response back if the route detects an incoming POST body:
$app->post('/users/filter/{param1}/{param2}/{param3}', function (Request $request, Response $response) {
$postBody = $request->getParsedBody();
if (is_array($postBody)) {
$denyMsg = "This endpoint does not accept POST body as a means to transmit data; please refer to the API documentation for proper usage.";
$denyResponse = $response->withJson($denyMsg, $status = null, $encodingOptions = 0);
return $profileData;
}
});

Laravel JWT tokens are Invalid after refresh them in a authentication JWT approach

EDIT:
Read the discussion about the bug at: https://github.com/tymondesigns/jwt-auth/issues/83
MY ORIGINAL QUESTION:
I'm implement with jwt-auth my protected resources that require an authenticated user with bellow code:
Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function() {
// Protected routes
});
When user 'sign in' on API an Authorization token is created, and sent on response Authorization header to client application that call the resource. So, client applications when intercept a Authorization token on header of any response, set a variable/session/whatever with this token value, to send again to API on next request.
The first request for a protected resource after 'login' works fine, but the next client application request to API with a refreshed token, gives the following error (API mount all responses in json format):
{
"error": "token_invalid"
}
What can be happen with refreshed tokens? My refresh token implementation (set as a after middleware) is wrong? Or isn't necessary to manually refresh all Authorization token that come with client apps requests?
UPDATE:
I update the jwt-auth RefreshToken middleware as propose here, but the token_invalid persist.
BUG:
I guess that I found what happens. Note that in the refresh method, old token is added to blacklist cache case enabled:
// Tymon\JWTAuth\JWTManager
public function refresh(Token $token)
{
$payload = $this->decode($token);
if ($this->blacklistEnabled) {
// invalidate old token
$this->blacklist->add($payload);
}
// return the new token
return $this->encode(
$this->payloadFactory->setRefreshFlow()->make([
'sub' => $payload['sub'],
'iat' => $payload['iat']
])
);
}
And note that in add to blacklist method the key is the jti param from old token payload:
// Tymon\JWTAuth\Blacklist
public function add(Payload $payload)
{
$exp = Utils::timestamp($payload['exp']);
// there is no need to add the token to the blacklist
// if the token has already expired
if ($exp->isPast()) {
return false;
}
// add a minute to abate potential overlap
$minutes = $exp->diffInMinutes(Utils::now()->subMinute());
$this->storage->add($payload['jti'], [], $minutes);
return true;
}
Thus, when has on blacklist method is called, the old token jti param is the same that the new, so the new token is in blacklist:
// Tymon\JWTAuth\Blacklist
public function has(Payload $payload)
{
return $this->storage->has($payload['jti']);
}
If you don't need the blacklist functionality just set to false on jwt.php configuration file. But I can't say if it expose to some security vulnerability.
Read the discussion about the bug at: https://github.com/tymondesigns/jwt-auth/issues/83
When I get this issue, the solution that I found to get my project working was to generate a new token with data from older token on each new request.
My solution, that works for me, is bad, ugly, and can generate more issues if you have many async requests and your API(or business core) server is slow.
For now is working, but I will investigate more this issue, cause after 0.5.3 version the issue continues.
E.g:
Request 1 (GET /login):
Some guest data on token
Request 2 (POST /login response):
User data merged with guest data on old token generating a new token
Procedural code example(you can do better =) ), you can run this on routes.php out of routes, I say that is ugly haha:
// ----------------------------------------------------------------
// AUTH TOKEN WORK
// ----------------------------------------------------------------
$authToken = null;
$getAuthToken = function() use ($authToken, $Response) {
if($authToken === null) {
$authToken = JWTAuth::parseToken();
}
return $authToken;
};
$getLoggedUser = function() use ($getAuthToken) {
return $getAuthToken()->authenticate();
};
$getAuthPayload = function() use ($getAuthToken) {
try {
return $getAuthToken()->getPayload();
} catch (Exception $e) {
return [];
}
};
$mountAuthPayload = function($customPayload) use ($getLoggedUser, $getAuthPayload) {
$currentPayload = [];
try {
$currentAuthPayload = $getAuthPayload();
if(count($currentAuthPayload)) {
$currentPayload = $currentAuthPayload->toArray();
}
try {
if($user = $getLoggedUser()) {
$currentPayload['user'] = $user;
}
$currentPayload['isGuest'] = false;
} catch (Exception $e) {
// is guest
}
} catch(Exception $e) {
// Impossible to parse token
}
foreach ($customPayload as $key => $value) {
$currentPayload[$key] = $value;
}
return $currentPayload;
};
// ----------------------------------------------------------------
// AUTH TOKEN PAYLOAD
// ----------------------------------------------------------------
try {
$getLoggedUser();
$payload = ['isGuest' => false];
} catch (Exception $e) {
$payload = ['isGuest' => true];
}
try {
$payload = $mountAuthPayload($payload);
} catch (Exception $e) {
// Make nothing cause token is invalid, expired, etc., or not exists.
// Like a guest session. Create a token without user data.
}
Some route(simple example to save user mobile device):
Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function () use ($getLoggedUser, $mountAuthPayload) {
Route::post('/session/device', function () use ($Response, $getLoggedUser, $mountAuthPayload) {
$Response = new \Illuminate\Http\Response();
$user = $getLoggedUser();
// code to save on database the user device from current "session"...
$payload = app('tymon.jwt.payload.factory')->make($mountAuthPayload(['device' => $user->device->last()->toArray()]));
$token = JWTAuth::encode($payload);
$Response->header('Authorization', 'Bearer ' . $token);
$responseContent = ['setted' => 'true'];
$Response->setContent($responseContent);
return $Response;
});
});

PHP Slim Framework REST API - Validating an access token before each route?

I've set up a RESTful API and a separate client website that makes calls to it.
The client website uses the Resource Owner Password Credentials grant type of oAuth2 to obtain an access token from the API (from https://myapi.com/v1/oauth/token)
Every other call to the API must contain the access token to access data.
Using Slim framework, how can I pick up the access token, query the database for the user belonging to the access token and have that user model available to the route?
I want to do something like this but I'm not exactly sure how...
function validateAccessToken() {
$access_token = $_GET["access_token"];
$user = \models\user::where("access_token", "=", $access_token)->first();
if($user === NULL) {
throw new exception("Invalid access token");
}
return $user
}
$app->get("/v1/emails", validateAccessToken(), function() use ($app) {
$emails = \models\emails::where("user_id", "=", $user->id)->toArray();
echo(json_encode($emails));
});
$validateAccessToken= function($app) {
return function () use ($app) {
$access_token = $app->request()->get("access_token");
$user = \models\user::where("access_token", "=", $access_token)->first();
if($user === NULL) {
$app->redirect("/errorpage");
}
};
};
$app->get("/v1/emails", $validateAccessToken($app), function() use ($app) {
// here you have to define $user once again
$access_token = $app->request()->get("access_token");
$user = \models\user::where("access_token", "=", $access_token)->first();
$emails = \models\emails::where("user_id", "=", $user->id)->toArray();
echo(json_encode($emails));
});

How to use FOSTwitterBundle to call Twitter API

I have installed FOSTwitterBundle and set up key on Twitter.
I create this Action:
public function twitterFirstAction(Request $request)
{
$twitter = $this->get('fos_twitter.service');
$authURL = $twitter->getLoginUrl($request);
$response = new RedirectResponse($authURL);
return $response;
}
It redirects to twitter and redirected back to twitterSecondAction()
public function twitterSecondAction(Request $request)
{
$twitter = $this->get('fos_twitter.service');
// now what
}
I checked the session, everything is fine. But now I get confuse how to make an API call. For example, list user's followers.
FOSTwitterBundle has fos_twitter.api service which is TwitterOAuth class which allows you to do get, post and delete requests.
So your second action can look like this:
public function twitterSecondAction(Request $request)
{
$twitter = $this->get('fos_twitter.api'); //
$idsOfFollowers = $twitter->get('followers/ids');
}
Please see twitter API documentation for possible actions.

How do I make an internal redirect in Silex / Symfony?

I have users getting auto-generated when they login via social media. I don't get their email so I want to make it that when they land on /whatever/page/after/login they see a screen that's like "Just enter email to continue on!"
I took a look at http://silex.sensiolabs.org/doc/cookbook/sub_requests.html and I'm either misreading or thinking I'd need to do it within a Silex\ControllerProviderInterface. I want this behavior to be for any request. Meanwhile if I make all my providers extend one, I'm not sure of the right way to cut out of a parent's connect without botching everything.
I also tried re-initializing everything similar to the answer here Unable to overwrite pathInfo in a Symfony 2 Request.
Here is the code I'm working with:
$app
->before(function (Request $request) use ($app) {
$token = $app['security']->getToken();
$app['user'] = null;
if ($token && !$app['security.trust_resolver']->isAnonymous($token)) {
$app['user'] = $token->getUser();
if (!$app['user']->isVerified()) {
$request->server->set('REQUEST_URI', '/signup');
$request->initialize($request->query->all(), $request->request->all(), $request->attributes->all(), $request->cookies->all(), $request->files->all(), $request->server->all(), $request->getContent());
}
}
});
I believe what you want to do is create a new Request object with the correct values and then tell that app to handle it.
If you don't care about preserving the request params from the original request then you can strip a lot of the extra stuff out.
$app
->before(function (Request $request) use ($app) {
$token = $app['security']->getToken();
$app['user'] = null;
if ($token && !$app['security.trust_resolver']->isAnonymous($token)) {
$app['user'] = $token->getUser();
if (!$app['user']->isVerified()) {
$subRequest = Request::create('/signup', 'GET', $request->attributes->all(), $request->cookies->all(), $request->files->all(), $request->server->all(), $request->getContent());
$subRequest->request = $request->request;
$subRequest->query = $request->query;
return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
});

Categories