I'm developing REST API server in PHP, which I plan to call by client application, but I want to prevent direct API accesss via browser.
E.g. say I have GET call on "HOST/api/article/id" which would return article with given id to the client application. But when I type "HOST/api/article/id" in my browser, the article shouldn't be returned - I want nothing to happen (for example just return empty page or 403).
Is this possible?
If yes, is it common practice? (I mean is it something one would normally want to do or is it obscure and/or violates HTTP/REST principles and should be avoided?)
If yes, how do I do it? (in PHP/.htaccess/etc.)
I know I could implement some kind of authorization (like API key) instead which would allow API execution only for the client applications, which I plan to do anyway.
(I'm kind of new to this so maybe my question doesn't make sense. Maybe I misunderstood something very basic about how REST/HTTP/whatever works. If so, please tell me.)
The normal approach would be:
Your client (using a public key), requests token from the server, token checks if key is valid and not blacklisted (you can expire/blacklist old keys if they are compromised)
Token is sent every time
Server only responds if there is a token
Depending what your requirements, there is a hacky way to implement this.
Have a variable, called "my_client" with value true
On each request from your application sent the variable in your headers.
Server only servers information if "my_client" variable is in the headers
The cons with this approach is, that is not really secure, because each person can see the requests they make. Therefor can notice this extra information.
Its so simple that you can write it for a minute, just as a test.
<?php
if(!$_SERVER['HTTP_MY_CLIENT']){
header("HTTP/1.1 403 FORBIDEN");
}
Extending on the concept of using a header variable, we can use it as "semi token", which will mean we will populate the value with a random value that only we can read.
So the concept is this:
Client -> Request random value
Client /sets value in each request header/
Client -> makes requests to the server.
<?php
/* A basic API token and authentication class. */
class SimpleToken
{
/* Creates a salt based on the passed key that is good for the current day */
public static function generateSalt($key)
{
return md5($key . date('Y-m-d'));
}
/* Crytographically combine the key and the salt to produce a token */
public static function generateToken($key, $content)
{
$package = $content . $key;
return crypt($package);
}
/* Generate a relatively strong SSL key */
public static function generateKey()
{
$config = array(
"digest_alg" => "sha512",
"private_key_bits" => 4096,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
);
//Create a private key
$res = openssl_pkey_new($config);
//Extract the private part of the key
openssl_pkey_export($res, $private_key);
//Shorten it up for use in an API
return md5($private_key);
}
/* Verify the authenticity of the passed key/token pair */
public static function isAuthentic($key, $content, $token)
{
$package = $content . $key;
if(crypt($package, $token) == $token)
{
return true;
}
else
{
return false;
}
}
}
Related
I want to send a request with or without 'Token' as a header.
If request has 'Token' as a header: if the user already has that item, it will return the item with the proper item_id of a specific user (based on its token), otherwise it will return null.
If request doesn't have 'Token' as a header: it will return the item with that item_id
I'm working with Zend Framework and in ItemResource I have this method:
public function fetch($id)
{
}
How can I check if my request has Token as a header or not and implement both cases inside fetch()?
Using Laminas API Tools it depends on wether you 're using a RPC or a REST resource. I will explain which tools the Laminas API Tools give you to evaluate the received header data.
You don 't have to reinvent the wheel, because Laminas API Tools has the received headers already at hand, when you 're in your fetch method.
Representational State Transfer (REST)
Rest resources normally extend the \Laminas\ApiTools\Rest\AbstractResourceListener class. This class listens for \Laminas\ApiTools\Rest\ResourceEvent. Fortunately, this event provides you with a request object that also contains the received header data.
<?php
declare(strict_types=1);
namespace Marcel\V1\Rest\Example;
use Laminas\ApiTools\Rest\AbstractResourceListener;
class ExampleResource extends AbstractResourceListener
{
public function fetch($id)
{
// requesting for an authorization header
$token = $this->getEvent()->getRequest()->getHeader('Authorization', null);
if ($token === null) {
// header was not received
}
}
}
As you can see the ResourceEvent returns a \Laminas\Http\Request instance when calling getRequest(). The request instance already contains all request headers you 've received. Just call getHeader with the given name and as second parameter a default value, which should be returned, when the header was not set. If there is no http_token header, you 'll get null as a result.
Remote Procedure Calls (RPC)
Since RPC requests are handled with a MVC controller class, you can get the request as easy as in a rest resource. Controller classes extend from \Laminas\Mvc\Controller\AbstractActionController, which already contains a request instance.
<?php
declare(strict_types=1);
namespace Marcel\V1\Rpc\Example;
use Laminas\Mvc\Controller\AbstractActionController;
class ExampleController extends AbstractActionController
{
public function exampleAction()
{
$token = $this->getRequest()->getHeader('Authorization', null);
if ($token === null) {
// token was not set
}
}
}
As you can see getting header data in rpc requests is as easy as in resource listeners. The procedure is the same because a request instance is also used here.
Conclusion
There is absolutely no need for coding things, that are already there. Just get the request instance from the event or the abstract controller and retrieve the header you want. Always keep in mind, that there are security aspects like CRLF injections, when dealing with raw data. The Laminas framework handles all this for you already.
Additionally you can check for all received headers by calling ->getHeaders() instead of ->getHeader($name, $default). You 'll get a \Laminas\Http\Header instance with all received headers.
You can get all HTTP header values by getallheaders() or just get the specific value by $_SERVER['HTTP_XXX'], in your case, replace XXX with Token, $_SERVER['HTTP_Token'].
Manual: https://www.php.net/manual/en/reserved.variables.server.php
public function fetch($id)
{
$token = $_SERVER['HTTP_Token'];
// do your busniess code
}
I am working on a Laravel app where I am building some API for other websites. But I am trying to make the implementation of my API as easy as possible. My expectation is that the user will only use this tag in the HTML head:
<script src="api.mydomain.com">
Now I have a controller on this URL that provides the source javascript with the content-type header, but before it goes there, the router will first execute my authentication middleware. Let's say it looks something like this:
public static $users = [
'client1.com',
'client2.com',
'client3.com'
];
public function handle(Request $request, Closure $next)
{
$origin = "HERE I NEED THE ORIGIN URL"; // e.g. client4.com
if ( !in_array($origin, self::$users) ) {
abort(401);
}
return $next($request);
}
As you can see from the code, I need to retrieve the $origin variable. So if a website client1.com will try to insert my javascript, it will successfully get the javascript code. If client4.com tries to access it, it will get a 401 error.
I found out methods with $_SERVER['HTTP_REFERER'] or Laravel's $request->server('HTTP_REFERER'), but this data might be spoofed, right?
In the best-case scenario, I would like to retrieve the original domain and when not available (e.g. from a private cURL request), I would like to get the IP address. And of course, I need it to be secure - clients1/2/3 paid for my API, others didn't.
How can I do it? Or is there any better method for origin authentication?
All that referer stuff can be spoofed.
Best way for paid API is to issue API calling key.
You API can display results or error depending if the client has proper API key and is Paid for.
You should also keep logs table for API calls with timestamp and clientID and IP addresses. So from time to time you can check if one of your paid client is sharing his key with others etc from call frequency and IP patterns.
Clean up this logs table from time to time to keep it small and efficient.
So I figured it out by adding headers (thanks for inspiration #jewishmoses) in the middleware handler. My Javascript is available basically to everyone, but it provides only a button, that tries to create a new element with an iframe inside (my app which also works as an API).
Let's say I have an associative array on the server, that I can dynamically fill from any database:
$clients = [
'client1' => 'paying-customer.com',
'client2' => 'also-paying-customer.com',
];
...my route for API is defined as 'api.mydomain.com/{client}' and 'api.mydomain.com/{client}/iframe' for iframed app. This handler takes care of adding headers:
public function handle(Request $request, Closure $next)
{
$client = $request->route('client',null);
$clientSet = $client !== null;
$clientAccepted = isset($clients[$client]);
if ( $clientSet and !$clientAccepted ) {
abort(401);
}
$response = $next($request);
if( $clientSet and isset($response->headers) and $response->headers instanceof ResponseHeaderBag){
$clientDomain = $clients[$client];
$response->headers->add([
'Content-Security-Policy' => "frame-ancestors https://*.$clientDomain/ https://$clientDomain/"
]);
}
return $response;
}
Now what might happen:
client1 successfully imports javascript from api.mydomain.com/client1, which will try to access api.mydomain.com/client1/iframe (also successfully)
client3 unsuccessfully tries to import javascript from api.mydomain.com/client3
client3 successfully imports javascript from api.mydomain.com/client1, which will try to access api.mydomain.com/client1/iframe (refused by headers)
Maybe there is a more elegant way to block loading the javascript, but providing my own app as API (in an iframe) is in my opinion secured enough because I can control who can use it and modern browsers will help me to stop the "thieves". This resource was most helpful to solve my problem.
I am using third party REST API in my SYMFONY 4.3 app. My app requires checking if token is valid before any request. When is the best place to check if the token is valid and if not try to refresh before request in symfony? Any before request filter in symfony exists? or is there global object when I can fetch all request and if header is 401 I can perform specific action
Now I have central point in my app and all requests are passed through this function. But in future when I will have other request not passed through this function I have to make next function etc... and I am searching place where put isTokenValid code, I am thining about place like " call this function before any request to API "
Should i Use it?
https://symfony.com/doc/current/event_dispatcher/before_after_filters.html#token-validation-example
public function prepareRequest($method, $endPoint) {
.........
// Users can have many tokens connected to different accounts on third party app
$apiTokens = $user->getApiTokens();
/** #var ApiToken $apiToken */
foreach ($apiTokens as $apiToken) {
if ($this->isTokenValid($apiToken)) {
............. make request with specifed apiToken
}
public function isTokenValid(ApiToken $token): bool
{
if token is not valid return false
if token date expired try to refresh token
if token is not valid or refreshing token fails return false else return true
}
The solution I'd like to suggest is to use lexik/jwt-bundle I use it in almost all of mine front-end authentication projects for example you can customize the default response (JWT token not found / not valid) to return the response you desire. You can create both anonymous true or false routes for your purpose I guess anonymous should be true even though your token expired you will extend its lifetime. In case you want some insights put a comment to this answer and I'll provide as best as I can
EDIT Originally I thought Oauth2 is the way to go but maybe it is not. I'll leave that out of this question for now as it is confusing things.
I'm creating a mobile app (Android/iOS). I'd like the user to enter their credentials (user/pass) in the mobile device which would then get sent to my server (Joomla CMS) to verify the credentials and create/send a token. I don't want to store the user/pass on the device just the token.
In addition this token needs to have a timeout to be refreshed when needed. Such as credentials have changed.
At this point I'm trying to figure out what the architecture of this will look like.
Are there any tutorials on how you can achieve this (ideally with Joomla)? Anything that someone could point me to?
You should post the username and password from the mobile app and from there on you should follow the solution provided in this question:
https://stackoverflow.com/a/2188969/900617
The end solution is to create my own Joomla component. Pretty much everything is in my controller. Not the final code but something like this will work.
defined('_JEXEC') or die;
jimport('joomla.application.component.controller');
class FooauthController extends JController
{
function __construct() {
// params
$jinput = JFactory::getApplication()->input;
$this->username = $jinput->get('user', '', 'STRING');
$this->password = $jinput->get('password', '', 'STRING');
$this->checkParameters();
}
private function checkParameters() {
// datatype checks
if ($this->username == '' || $this->password == '') {
header('HTTP/1.1 400 Bad Request', true, 400);
}
}
private function createToken() {
// token generation - what Joomla does (just an example)
jimport('joomla.user.helper');
$salt = JUserHelper::genRandomPassword(32);
$crypted = JUserHelper::getCryptedPassword($password, $salt);
$cpassword = $crypted.':'.$salt;
return $cpassword;
}
function execute() {
// Get the global JAuthentication object
jimport( 'joomla.user.authentication');
$auth = & JAuthentication::getInstance();
$credentials = array( 'username' => $this->username, 'password' => $this->password );
$options = array();
$response = $auth->authenticate($credentials, $options);
// success
if ($response->status === JAUTHENTICATE_STATUS_SUCCESS) {
$response->status = true;
echo json_encode($this->createToken());
} else {
// failed
$response->status = false;
echo json_encode($response);
}
}
}
This represents a component called com_fooauth. Now the native app will send a query like this:
http://www.myhost.com/index.php?option=com_fooauth&user=username&password=pass&format=raw
Kind of a short cut to put everything in the controller, but hopefully you get the idea.
I hope that I understand correctly your use case.
If you want to use oAuth, then your mobile apps are considered as the oAuth-client.
Your "server" holds the "protected resources", and it can be used only with oAuth access-token, so it is called "resource server". Now you want something to supply this access-token, so this is the identity-provider, AKA authentication server, e.g. Facebook, Google, (or implement one by your own).
The flow is (generally): the user (mobile app) tries to reach a protected resource; since it has no token, he is being redirected to the auth-server. the latter is responsible for the user/password login page, and creating the token.
If it is true - you still can implement everything by your own, without using Facebook/Google APIs, because oAuth has SPECs. However, it can be easier for you to use the providers' packages.
EDIT: reconsider the usage of oAuth
You use oAuth only if you want your webapp to support oAuth SPEC. There are several benefits, one of them is that you can use 3rd party identity provider, e.g. Yahoo! and use their identities without managing them. So if I have a user in Yahoo!, I can use your app without additional registrations (your app will have to support access-tokens from Yahoo!). But in your case, you are about to implement all the logic of identity-provider (forgot password, change password, registration, etc) plus supporting oAuth - and all of this without enjoying the benefits of oAuth at all! So - you have to reconsider the usage of oAuth...
You need to use their APIs as a base. They aren't going to just let you build your own API that connects to their database, that to them would look more like a password cracker than an API.
This isn't Joomla or a tutorial, (and I'm very rusty in php) that said...
First a few caveats:
* memcache isn't secure & this implementation has you putting username / password in: Be sure that it is safely behind a firewall, or else encrypt it first. Happy to give some pointers on that if you need it.
* memcache isn't guaranteed not to drop data if it runs out of memory. In practice it is reliable, but your app should handle that gracefully. If you don't want to lose data like that, just substitute something like couchbase for memcache.
* just returning a token in response to a login probably isn't super useful. I'd json-ize the token along with stuff like the user name, and any other info to get the app up and running without needing to make a second API call.
* the code below doesn't handle error cases, I can call them all out in more detail if that isn't obvious to you.
If it were me, I'd just use memcache to persist the tokens & map that token to the username & password that was originally passed. You can use the memcache time to live to get your expiration for free.
Send username / password to the server (ideally over https).
Create a random string or guid (eg: http://php.net/manual/en/function.uniqid.php or http://www.lateralcode.com/creating-a-random-string-with-php/) , this is your token
Store the username / password in memcache with that token as a key
Set a timeout
$token = createToken("user1234", "pass2324");
print "Token: $token \n\n";
$credentials = credtialsFromToken($token);
print "Credentials from the token: ";
var_dump($credentials);
print "\n\n";
function setup() {
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");
}
function createToken($user, $pass) {
$TOKEN_EXPIRE_TIME=60 * 60 * 24 * 30;
$credentials = array(
"user" => $user,
"pass" => $pass,
);
$token = uniqid( );
memcache_set($token, credentials, 'some variable', 0, 30);
return $token;
}
function credtialsFromToken($token) {
$credentials = memcache_get($token);
return $credentials;
}
If the token is incorrect or expired, they get an null credentials back and have to login.
Edit: cleaned it up into functions that appear to work in php...
I have developed an ASP.NET Web Service that performs queries to a database. Now I am developing a PHP Web site that consumes the Web Service for anything that requires access to the database. I have written the following function which creates a new SoapClient to consume the Web Service:
function get_soap_client()
{
if (!file_exists('wsdl_path.txt')
return NULL;
if (!$file = fopen('wsdl_path.txt', 'r'))
return NULL;
$path = fgets($file);
fclose($file);
return new SoapClient($path);
}
I have already tested this function as means to get a client to a stateless Web Service, and it does work. For example, this is my Web site's login function (whose control flow is not affected by state):
function try_login($user, $pass)
{
$client = get_soap_client();
// ASP.NET Web Services encapsulate all inputs in a single object.
$param = new stdClass();
$param->user = $user;
$param->pass = $pass;
// Execute the WebMethod TryLogin and return its result.
// ASP.NET Web Services encapsulate all outputs in a single object.
return $client->TryLogin($param)->TryLoginResult;
}
Which in turn calls the following WebMethod:
[WebMethod(EnableSession = true)]
public bool TryLogin(string user, string pass)
{
SqlParameter[] pars = new SqlParameter[2];
pars[0] = new SqlParameter("#User", SqlDbType.VarChar, 20);
pars[0].Value = user;
pars[1] = new SqlParameter("#Pass", SqlDbType.VarChar, 20);
pars[1].Value = pass;
// The Stored Procedure returns a row if the user and password are OK.
// Otherwise, it returns an empty recordset.
DataTable dt = Utilities.RetrieveFromStoredProcedure('[dbo].[sp_TryLogin]', pars);
if (dt.Rows.Count == 0) // Wrong user and/or password
{
Context.Session.Abandon();
return false;
}
else
return true;
}
However, I don't know whether the Web Service's Context.Session data is preserved between consecutive requests made from SoapClient objects retrieved by get_soap_client(). So I have the following questions:
Is the Web Service's Context.Session data preserved between consecutive requests made from a SoapClient object retrieved by get_soap_client()?
Is the Web Service's Context.Session data preserved between consecutive requests made from different SoapClient objects retrieved on the fly as I need them?
If the answer to the previous question is "no", is there any way to serialize the SoapClient's session data into a PHP state variable?
Ah, you mean is it sending the SESSION ID. I imagine you'll need to call __setCookie manually to set a cookie with the SESSION ID to your web service.
http://php.net/manual/en/soapclient.setcookie.php