JWT Passthrough to specific method of api - php

From the question above, I have api/user that contains get, post, put, and delete methods. Is it possible to have passthrough on a specific method?
Example, the public method only is get and the rest needs a token to use that method?
Thank you for your answer.
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => ["/api", "/admin"],
"passthrough" => ["/api/login", "/admin/ping", "/api/user"],
"algorithm" => "HS256",
"secret" => getenv("JWT_SECRET"),
"callback" => function ($request, $response, $arguments) use ($container) {
$container["jwt"] = $arguments["decoded"];
},
"error" => function ($request, $response, $arguments) {
$data["status"] = "error";
$data["message"] = $arguments["message"];
return $response
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}]));

By default JWT Authentication middleware does not authenticate OPTIONS requests. To also allow unauthenticated GET requests you can manually add it to the RequestMethodRule. Your example code would become something like following.
require __DIR__ . "/vendor/autoload.php";
$app = new \Slim\App;
$container = $app->getContainer();
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => ["/api"],
"secret" => getenv("JWT_SECRET"),
"callback" => function ($request, $response, $arguments) use ($container) {
$container["jwt"] = $arguments["decoded"];
},
"rules" => [
new \Slim\Middleware\JwtAuthentication\RequestMethodRule([
"passthrough" => ["OPTIONS", "GET"]
])
],
"error" => function ($request, $response, $arguments) {
$data["status"] = "error";
$data["message"] = $arguments["message"];
return $response
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
]));
$app->get("/api/user", function ($request, $response) {
print "Hello\n\n";
});
$app->post("/api/user", function ($request, $response) {
print "Hello\n\n";
});
$app->run();
This would yield.
$ curl --request GET --include http://127.0.0.1:8080/api/user
HTTP/1.1 200 OK
Host: 127.0.0.1:8080
Connection: close
X-Powered-By: PHP/7.0.12
Content-Type: text/html; charset=UTF-8
Content-Length: 7
Hello
$ curl --request POST --include http://127.0.0.1:8080/api/user
HTTP/1.1 401 Unauthorized
Host: 127.0.0.1:8080
Connection: close
X-Powered-By: PHP/7.0.12
Content-Type: application/json
Content-Length: 59
{
"status": "error",
"message": "Token not found"
}

Yes, you can use Slim Middleware and group authorized routes together and add the middleware to the group:
$validateUser = function($request,$response,$next) {
$token = $_COOKIE['token'];
$token = JWT::decode($token,$secret,['HS256']);
if ($token->user->isAdmin) {
return $next($request,$response);
}
return $response->withStatus(403)->withJson(array('message' => 'Forbidden'));
};
$app->get('/api/user',function($request,$response) {
return $response->withJson(array('message' => 'Public route'));
});
$app->group('/api/user',function() {
$this->delete('','');
$this->post('','');
$this->patch('','');
})->add($validateUser);

Related

Slim 3 Integration Test For POST Method Always Not Included Header

I'm currently developing an application written using Slim 3 framework, and trying to use the TDD concept, but I'm facing several issues, including when using the test for the POST method, the headers that I embed are always considered missing.
Below is my code
<?php
namespace Tests\routes\shopify;
use PHPUnit\Framework\TestCase;
use Slim\App;
use Slim\Http\Environment;
use Slim\Http\Headers;
use Slim\Http\Request;
use Slim\Http\RequestBody;
use Slim\Http\Response;
use Slim\Http\UploadedFile;
use Slim\Http\Uri;
class ShopifyRoutesTest extends TestCase
{
private $app;
protected function setUp(): void
{
// Use the application settings
$settings = require __DIR__ . '/../../../src/settings.php';
// Instantiate the application
$this->app = new App($settings);
// Set up dependencies
$dependencies = require __DIR__ . '/../../../src/dependencies.php';
$dependencies($this->app);
// Register middleware
$middleware = require __DIR__ . '/../../../src/middleware.php';
$middleware($this->app);
// Register routes
$routes = require __DIR__ . '/../../../src/app/routes/api/shopify/routes.php';
$routes($this->app);
}
public function testPostSyncProductBySkuWithEmptyApikeyShouldReturnBadRequest()
{
// Create a mock environment for testing with
$environment = Environment::mock();
$uri = Uri::createFromString("/channel/shopify/v1/product/sync-by-sku");
$headers = new Headers(array(
"Content-Type" => "application/json",
"Authorization" => "client-apikey",
"x-api-key" => ""
));
$cookies = [];
$serverParams = $environment->all();
$body = new RequestBody();
$uploadedFiles = UploadedFile::createFromEnvironment($environment);
// Set up a request object based on the environment
$request = new Request("POST", $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
$reqBody = array(
"shop_name" => "STORE ABCE",
"sku" => "SKU-001"
);
$body->write(json_encode($reqBody));
// Add request data, if it exists
$request = $request->withParsedBody($reqBody);
// Set up a response object
$response = new Response();
$response = $this->app->process($request, $response);
self::assertEquals(400, $response->getStatusCode());
self::assertStringContainsString("Header: x-api-key cannot be empty", $response->getBody());
}
protected function tearDown(): void
{
$this->app = null;
}
}
the first assertion succeeds with a value of 400, but in the second assertion its fails, the string doesn't contain the value "Header: x-api-key cannot be empty" but instead this
Failed asserting that '{\n
"error": "AUTH_FAIL",\n
"errorDetail": "Header: Authorization, x-api-key required",\n
"status": 400,\n
"message": "BAD_REQUEST",\n
"data": ""\n
}' contains "Header: x-api-key cannot be empty".
the strange thing is that when I var_dump($request->getHeaders()) the headers value of the request I made it turns out to be there
array(3) {
["Content-Type"]=>
array(1) {
[0]=>
string(16) "application/json"
}
["Authorization"]=>
array(1) {
[0]=>
string(13) "client-apikey"
}
["x-api-key"]=>
array(1) {
[0]=>
string(0) ""
}
}
I've also tried testing my API endpoint using Postman, and the results are as expected
Request
curl --location --request POST 'http://localhost:8080/channel/shopify/v1/product/sync-by-sku' \
--header 'Authorization: client-apikey' \
--header 'x-api-key: 1293129382938' \
--header 'Content-Type: application/json' \
--header 'Cookie: PHPSESSID=tll8s24tp253rda1harv0koapi' \
--data-raw '{
"shop_name" : "STORE ABC",
"sku" : "SKU-991"
}'
Response
{
"error": "AUTH_FAIL",
"errorDetail": "Header: x-api-key cannot be empty",
"status": 400,
"message": "BAD_REQUEST",
"data": ""
}
Also I've read the answer from stakoverflow as described here Mock Slim endpoint POST requests with PHPUnit
But still I can't find the solution, the header is always presumed to be missing. I really appreciate the solution to this problem, thank you in advance
finally after figuring out the structure and behavior of the Header and also Request in Slim 3, the Header class in Slim 3 always makes the key value to lower-case, I don't know what that means, but finally I need to adjust this behavior in my middleware, from which previously used $request->getHeaders() to $request->getHeaderLine() and also $request->hasHeader(), $request->getHeaders() made the header value upper-case and added HTTP_ to the front of the key
in my case this is the cause of the problem, because the request I use in the unit test must pass the lower-case value and don't have HTTP_ at the front of the key, so the middleware assumes that the key that should exist has never existed
Middleware Before
// Common Channel Auth with client-apikey
$app->add(function (Request $request, Response $response, callable $next) use ($container) {
$uri = $request->getUri();
$path = $uri->getPath();
$headers = $request->getHeaders();
$arrayPath = explode("/", $path);
if ($arrayPath[1] == "channel" && $arrayPath[3] == "tools")
{
return $next($request, $response);
}
elseif ($arrayPath[1] == "channel" && $arrayPath[4] != "webhook")
{
/** #var ClientRepository $clientRepository */
$clientRepository = $container->get("ClientRepository");
// Get Header With Name x-api-key & Authorization
if (isset($headers["HTTP_AUTHORIZATION"]) && isset($headers["HTTP_X_API_KEY"]))
{
if ($headers["HTTP_AUTHORIZATION"][0] == "client-apikey")
{
$reqClientApikey = $headers["HTTP_X_API_KEY"][0];
if (v::notBlank()->validate($reqClientApikey))
{
if ($clientRepository->findByClientApiKey($reqClientApikey))
{
return $next($request, $response);
Middleware After
// Common Channel Auth with client-apikey
$app->add(function (Request $request, Response $response, callable $next) use ($container) {
$uri = $request->getUri();
$path = $uri->getPath();
$arrayPath = explode("/", $path);
if ($arrayPath[1] == "channel" && $arrayPath[3] == "tools")
{
return $next($request, $response);
}
elseif ($arrayPath[1] == "channel" && $arrayPath[4] != "webhook")
{
/** #var ClientRepository $clientRepository */
$clientRepository = $container->get("ClientRepository");
// Using $request-hasHeader & $request->getHeaderLine instead of $headers["HTTP_AUTHORIZATION"]
if ($request->hasHeader("authorization") != null && $request->hasHeader("x-api-key") != null)
{
if ($request->getHeaderLine("authorization") == "client-apikey")
{
$reqClientApikey = $request->getHeaderLine("x-api-key");
if (v::notBlank()->validate($reqClientApikey))
{
if ($clientRepository->findByClientApiKey($reqClientApikey))
{
return $next($request, $response);
}
Unit Test
public function testPostSyncProductBySkuWithEmptyApikeyShouldReturnBadRequest()
{
// Create a mock environment for testing with
$environment = Environment::mock();
$uri = Uri::createFromString("/channel/shopify/v1/product/sync-by-sku");
$headers = new Headers([
"Content-Type" => "application/json",
"Authorization" => "client-apikey",
"x-api-key" => ""
]);
$cookies = [];
$serverParams = $environment->all();
$body = new RequestBody();
$uploadedFiles = UploadedFile::createFromEnvironment($environment);
// Set up a request object based on the environment
$request = new Request("POST", $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
$reqBody = array(
"shop_name" => "STORE ABCE",
"sku" => "SKU-001"
);
$body->write(json_encode($reqBody));
// Add request data, if it exists
$request = $request->withParsedBody($reqBody);
// Set up a response object
$response = new Response();
$response = $this->app->process($request, $response);
self::assertEquals(400, $response->getStatusCode());
self::assertStringContainsString("Header: x-api-key cannot be empty", $response->getBody());
}
Once again, I hope this mistake I made will be a record for others, thank you

Ionic 5 Get Request to Slim API

I would like to make a get request from my Ionic app to an API build with the Slim Framework.
This is the code of the API:
<?php
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json');
?>
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use Tuupola\Middleware\HttpBasicAuthentication;
require 'vendor/autoload.php';
$jwt_secret = '**************';
$app = new Slim\App;
$app->add(new Tuupola\Middleware\JwtAuthentication([
"path" => "/api",
"attribute" => "jwt",
"secret" => $jwt_secret, "error" => function ($response, $arguments) {
$data["status"] = "error";
$data["message"] = $arguments["message"];
return $response
->withHeader("Content-Type", "application/json")
->withHeader("Access-Control-Allow-Origin", "*")
->getBody()->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
]));
$app->get('/api/hello', function (Request $request, Response $response, array $args)
{
$decoded = $request->getAttribute("jwt");
$response->getBody()->write(json_encode(array("status"=> "200", "message" => "HELLO ".$decoded['uid'] ." - " . $decoded['cus'])));
return $response;
});
$app->get('/', function (Request $request, Response $response, array $args) {
$response->getBody()->write(json_encode(array("status"=> "200", "message" => "Welcome to the API")));
return $response;
});
$app->run();
?>
When I'm testing with postman the API works fine. But when I'm trying to call it with the HTTPClient in Ionic, it doesn't work. This is my Ionic Code:
import { Component } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
#Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
constructor(private http: HttpClient)
{
}
sendRequest()
{
this.http.get('http://localhost/slim3',).subscribe((data)=>console.log(data));
}
}
The Error message is the following:
:8100/home:1 Access to XMLHttpRequest at 'http://localhost/slim3' from origin 'http://localhost:8100' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
core.js:6014 ERROR HttpErrorResponse {headers: HttpHeaders, status: 0, statusText: "Unknown Error", url: "http://localhost/slim3", ok: false, …}
How can I fix it? Thanks!
You must enable CORS(What is CORS?) in Slim Framework. Check http://www.slimframework.com/docs/v3/cookbook/enable-cors.html
Add this before $app->run(); (replacing http://mysite by your url, including port)
$app->options('/{routes:.+}', function ($request, $response, $args) {
return $response;
});
$app->add(function ($req, $res, $next) {
$response = $next($req, $res);
return $response
->withHeader('Access-Control-Allow-Origin', 'http://mysite')
->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
});

Slim Framework : unable to use JWT token

I am working on a slim REST API and I want to secure it with JWT Token. I try a lot of tutorials and I am anable to make things work.
I use :
slim 4.*
slim/psr7 0.6.0
tuupola/slim-jwt-auth ^3.4
tuupola/cors-middleware ^1.1
Ubuntu 19.10 and Xampp
I have have 2 routes (POST /login and GET /api/test)
I want to be able to use the /login route without token and the other one with a token.
So I wrote :
$app->add(new Tuupola\Middleware\JwtAuthentication([
"path" => "/api",
"secret" => getenv ("SPROUTCH_TOKEN"),
"error" => function ($request, $response, $arguments) {
$data["status"] = "error";
$data["message"] = $arguments["message"];
return $response
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
},
]));
In that case nothing is secure so I tried this :
$app->add(new Tuupola\Middleware\JwtAuthentication([
"secret" => getenv ("SPROUTCH_TOKEN"),
"error" => function ($request, $response, $arguments) {
$data["status"] = "error";
$data["message"] = $arguments["message"];
return $response
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
},
]));
And of course I can't access anything.
The proble was just that the "path" key takes only absolute path

Header can not set - SlimFramework

I have the same problem again.
Old post here
I have a angular app and SlimFramework for api connect.
Local it works fine but when i publish to my Website come the error that my Header no set.
But the info on the API testing tool says it's allowed from * IP.
Can someone help me?
Here a valid token: Basic TyOSZcfBwMC6DR9kbAWeMnPmhF4ohZu2n9LccQEyt6uXNt8PTT
Thx
$app = new \Slim\App(["settings" => $config]);
$container = $app->getContainer();
$app->options('/{routes:.+}', function ($request, $response, $args) {
return $response;
});
$app->add(function ($req, $res, $next) {
$response = $next($req, $res);
return $response
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE, PUT');
});
$container['logger'] = function($c) {
$logger = new \Monolog\Logger('my_logger');
$file_handler = new \Monolog\Handler\StreamHandler("../../logs/app.log");
$logger->pushHandler($file_handler);
return $logger;
};
$app->get('/token', function ($request, $response){
$db = new DbOperation();
if (!$request->hasHeader('Authorization')) {
return $response->withJson([
"success"=> false,
"message" => "Header not set.",
"textcode"=> "MSG2"
], 401);
}
$token = $request->getHeader('Authorization');
if($db->checkToken($token[0])){
$user = $db->userInfo($token[0]);
if($db->checkActivate($user['auth_user'])){
if($db->checkExpired($user['auth_user'])){
return $response->withJson([
"success"=> false,
"message" => "The validity of the login has expired. If you have any questions, please contact the administrator..",
"textcode"=> "MSG6"
], 401);
} else {
return $response->withJson(["success"=> true], 200);
}
} else {
return $response->withJson([
"success"=> false,
"message" => "This account has not yet been activated.",
"textcode"=> "MSG8"
], 401);
}
} else {
return $response->withJson([
"success"=> false,
"message"=>'Invalid token',
"textcode"=> "MSG1"
], 403);
}
});
Your basic auth credentials do not decode into anything meaningful. PHP tends to silently ignore Authorization headers which it thinks are malformed. Try with something like Basic dGVzdDp0ZXN0 which decodes into test:test.
Workaround for this has however been added to Slim starting from version 3.5.0. Upgrading your Slim installation might also help.

How to get authentication with jwt slim middleware?

This code works, but how can I get the permissions to see the /api content with a get request??
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require 'vendor/autoload.php';
$app = new \Slim\App();
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => "/api",
"secret" => "1234"
]));
$app->get('/api', function (Request $request, Response $response) {
echo "Hi";
});
$app->get('/teste', function (Request $request, Response $response) {
echo "Hi";
});
$app->run();
1. Generate Token
Using firebase/php-jwt
$payload = [
"sub" => "user#example.com"
];
$token = JWT::encode($payload,'JWT-secret-key');
2. .htaccess Changes
If using Apache add the following to the .htaccess file. Otherwise PHP wont have access to Authorization: Bearer header
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
3. Middleware
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => "/api",
"passthrough" => ["/teste"],
"secret" => "JWT-secret-key",
"secure" => false,
"callback" => function ($request, $response, $arguments) use ($container) {
$container["jwt"] = $arguments["decoded"];
},
"error" => function ($request, $response, $arguments) {
$data["status"] = "0";
$data["message"] = $arguments["message"];
$data["data"] = "";
return $response
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
]));
4. Correct Request
5. Wrong Token Request
Reference Link
i used Authorization: Bearer Mykey , the key need to be encode in jwt mode

Categories