I'm creating a project which uses CakePHP framework, and the PHP OAuth module.
The reason I'm using the module over a Vendor plugin is because the APIs need to have data/custom headers sent to them or else they generate 500s, and I know the standard PHP module does this well.
The issue is that when I use OAuth->fetch("http://www.example.com" ...), CakePHP redirects the external fetch request to my localhost (which I'm developing on), thus resulting in no data being generated.
It looks like the only way I can get external data is by using CakePHP's HTTPSocket class, but that doesn't allow me to send that data I need to send to the OAuth provider.
Does anyone know how to turn off this routing, or if I should be doing something differently?
UPDATE : Currently code is as follows:
public function createClient() {
$client = new OAuth(
'key',
'secret'
);
$client->disableSSLChecks();
if ($accessToken = $this->getAccessToken() !== false) {
$client->setToken(
$accessToken[$this->accessTokenKeyKeyname],
$accessToken[$this->accessTokenSecretKeyname]
);
}
return $client;
}
$url = 'http://example.com';
$client = $this->createClient();
$client->fetch($url, null, OAUTH_HTTP_METHOD_GET);
The request/access tokens are generated successfully, but the fetch continues to redirect to localhost, instead of example.com (example.com being used as an example URL).
Related
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'm currently building an AngularJS application with a PHP backend. The routing is done using Slim PHP and I've found an AngularJs module to do token-based authentication. In the module example for the backend they use Laravel and a client called GuzzleHttp\Client(). Now, I'm not sure what GuzzleHttp do that Slim PHP don't (if any) but I'm trying to follow along their example but I don't want to install 2 frameworks that could essentially do the same thing.
So I have my routing done so that when a request is made to the backend (auth/google) it'll do this:
public function google()
{
$app = \Slim\Slim::getInstance();
$request = $app->request()->getBody();
$body = json_decode($request);
$accessTokenUrl = 'https://accounts.google.com/o/oauth2/token';
$peopleApiUrl = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect';
$params = array(
'code' => $body->code,
'client_id' => $body->clientId,
'redirect_uri' => $body->redirectUri,
'grant_type' => 'authorization_code',
'client_secret' => GOOGLE_SECRET
);
$client = new GuzzleHttp\Client();
// Step 1. Exchange authorization code for access token.
$accessTokenResponse = $client->post($accessTokenUrl, ['body' => $params]);
$accessToken = $accessTokenResponse->json()['access_token'];
$headers = array('Authorization' => 'Bearer ' . $accessToken);
// Step 2. Retrieve profile information about the current user.
$profileResponse = $client->get($peopleApiUrl, ['headers' => $headers]);
$profile = $profileResponse->json();
// Step 3a. If user is already signed in then link accounts.
if (Request::header('Authorization'))
{
$user = User::where('google', '=', $profile['sub']);
if ($user->first())
{
return Response::json(array('message' => 'There is already a Google account that belongs to you'), 409);
}
$token = explode(' ', Request::header('Authorization'))[1];
$payloadObject = JWT::decode($token, Config::get('secrets.TOKEN_SECRET'));
$payload = json_decode(json_encode($payloadObject), true);
$user = User::find($payload['sub']);
$user->google = $profile['sub'];
$user->displayName = $user->displayName || $profile['name'];
$user->save();
return Response::json(array('token' => $this->createToken($user)));
}
// Step 3b. Create a new user account or return an existing one.
else
{
$user = User::where('google', '=', $profile['sub']);
if ($user->first())
{
return Response::json(array('token' => $this->createToken($user->first())));
}
$user = new User;
$user->google = $profile['sub'];
$user->displayName = $profile['name'];
$user->save();
return Response::json(array('token' => $this->createToken($user)));
}
}
Now this won't work because I don't have GuzzleHttp installed but my question is: can I do this in Slim PHP or do I need GuzzleHttp to complement it?
Guzzle is a code based HTTP client package/framework which also contains DOM crawling functionality not a micro-framework, thus it is not analogous to Slim.
From their Readme:
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.
Slim does not provide this functionality directly because it doesnt fall under what Slim is meant to do which is transform HTTP requests into HTTP responses (and the core things that need to happen in between).
Since your example is in Guzzle and it implements what you are trying to do i would probably use Guzzle. However, you could do the same types of thing (ie. interact with an external web service) using cURL, ext/http, or another HTTP client package. There are several.
Slim is a micro-framework that provides basically client requests and responses, coming from the application. It means that Slim responds to request, it doesn't make requests to external HTTP, for instance. The idea of Slim is to provide routes, do stuff when it comes and responds to the client.
If you need to use external calls, you'll need to use any HTTP Client, that'll provide the "ability" to make requests and threat with the response. You can use curl natively (all the others are only "interfaces" to curl) or a lib.
While Slim and Guzzle have a lot in common ie they both deal with psr-7 requests and responses
There is one key difference
Slim Deals with processing requests and sending responses
Guzzle Deals with sending requests and processing responses
as such they are not interchangeable and deal with opposite ends of the communication pipeline
so if your processing requests that someone has sent to you you need slim or something similar
if you are sending requests to someone else then you need guzzle or something similar
I build a application in CI. Here I need a API code from a third-party website/URL. I have not any idea how to receive that code into my controller.
$user_email = "jondoe#appsapi.com.au"
Example - https://www.sample.com.au/api/apps/auth{$user_email}
When I enter this Example domain into the browser it provides a API key. Like - A9D5w9pL How to I get that into my Controller.
Controller -
public function auth_with_api() {
//https://www.sample.com.au/api/apps/auth{$user_email}
// here I require the API key. How can I receive it here.
}
You can do that by cURL. Just download the codeIgniter cURL library from here
http://getsparks.org/packages/curl/show (Dead Link)
https://github.com/philsturgeon/codeigniter-curl (Update Link - Git Repo)
Put this library file in the libraries folder.
So now in the Controller -
public function auth_with_api()
{
$this->load->library('curl');
$user_email = "jondoe#appsapi.com.au"
$api_key = $this->curl->simple_get('https://www.sample.com.au/api/apps/auth{$user_email}'); // $api_key now get the value A9D5w9pL
// now you can use this $api_key in this controller.
}
Edit (Another way) -
Now you can use Guzzle. It is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.
Please check out the Guzzle Documentation -
http://docs.guzzlephp.org
public function auth_with_api()
{
$user_email = "jondoe#appsapi.com.au"
$client = new \GuzzleHttp\Client();
$resposne = $client->request('GET', 'https://www.sample.com.au/api/apps/auth{$user_email}');
$api_key = $resposne->getBody();
// $api_key now get the value A9D5w9pL
}
If you have any problem. Please let me know.
I am building up a user creation page (controller/module: User) which has UI controls (DOJO filterselectbox, username, etc.). The UI controls get populated with a Json service deployed as module (module name/controller) myService, and action populatelist().
populatelist returns data as Json to client and the client dojo ui elements use that as a memory store.
I have 2 modules, User and myService. For the User module, I have setup default page as register and in register.phtml as given below. I have added logic for user input validation and data post.
module.config.php of module: User
'defaults' => array(
'controller' => 'User\Controller\User',
'action' => 'register',
),
Json is registered in module myService. register.phtml makes a call like:
myservice = new dojo.rpc.JsonService("myService/populatelist");
var dojoDeferredObject=myservice.getCategoryList();
//comment: getCtegoryList is actual method of remote object which returns the json data
When I open the url as http://localhost/user, any reference to myService JSONRPC call works perfectly fine: it parses the JSON call as http://localhost/myService/populatelist and I get the data I need.
When I access the url as http://localhost/user/register, things fail with 404 page not found exception for every Json RPC call. Reason is, the RPC call is going on a non-existent path, i.e. http://localhost/user/myService/populatelist instead of http://localhost/myService/populatelist.
Somewhere I have missed a configuration which is resulting in this issue. I do not want to hardcode path of Json service Module myService.
I believe the problem is this line:
$server->setTarget('myService/populatelist');
in the below code, used to set up the Json Service. This is adding up to the path which does not exist. But I am not sure how can I control it as I want a separate module for Json service.
$class = "MOCAPI\Model\MOCGuest";
$server = new Server();
$server->setClass($class);
//echo $_SERVER['REQUEST_METHOD'];
if ('GET' == $_SERVER['REQUEST_METHOD']) {
$server->setTarget('myService/populatelist')
->setEnvelope(Smd::ENV_JSONRPC_2);
$smd = $server->getServiceMap();
// Set Dojo compatibility:
$smd->setDojoCompatible(true);
header('Content-Type: application/json');
echo $smd;
return $this->getResponse();
} else {
//$server->handle();
}
You should use routes and url() helper to build urls and relative and absolutes paths, instead of raw 'myService/populatelist'.
Check the docs at https://framework.zend.com/manual/2.4/en/modules/zend.view.helpers.url.html (version 2.4, but it almost the same in zf2.* and zf3).
I have a website (call it siteA) I used php and Zend, I am trying to implement an api to access the restricted areas of my site(SiteA) from another(call it SiteB).
I would like to be able to authenticate user on siteB with SiteB credentials, have an html iframe to siteA where the user has been Authenticated via the API.
I am new and don't really know if I am headed in the right direction.
So far I was thinking about using a restful api, and outside the frame the user is validated but in the frame the user is not.
Suggestions and help?
And my code is as follows:
----- SiteB user/index.php ----
<html>
<body>
<?php
$postdata = http_build_query(
array(
'userid' => 'someid',
'key' => 'somekey',
'public' => '1'
)
);
$opts = array('http' =>
array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postdata
)
);
$context = stream_context_create($opts);
file_get_contents('https://www.siteA.com/api', false, $context);
?>
<iframe src="https://www.siteA.com/" width="950" height="700"></iframe>
</body>
</html>
----- SiteA ---------
<?php
class ApiController extends Zend_Controller_Action
{
public function init()
{
/* Initialize action controller here */
$this->_helper->viewRenderer->setNoRender(true);
$this->_helper->layout->disableLayout();
}
public function indexAction() {
if($this->getRequest()->isPost()){
$data = $this->_request->getPost();
$this->_authenticateApi($data);
}
else{
//error
}
}
public function _authenticateApi($data){
$db = Zend_Registry::get('db_local');
$authAdapter = new Zend_Auth_Adapter_DbTable($db);
$authAdapter->setTableName('sometable');
$authAdapter->setIdentityColumn('id');
$authAdapter->setCredentialColumn('key');
$authAdapter->setIdentity($data['id']);
$authAdapter->setCredential($data['key']);
$auth = Zend_Auth::getInstance();
$result = $auth->authenticate($authAdapter);
if($result->isValid()){
if($data['public'] == "1"){
Zend_Session::rememberMe(1209600);
}else{
Zend_Session::forgetMe();
}
return true;
} else {
return false;
}
}
I have faced this problem recently. Is there a reason you must use an iframe? It feels kind of hackish to me.
There are lots of ways to do this, but in my reading, it seems like a Best Practice is to make the API require clients to authenticate using HTTP Auth Basic, and to use HTTPS (an SSL Certificate) to encrypt the connection. The API does not manage sessions in this case! Every request contains the Auth Basic header, just like a regular web browser would do if you were directly browsing your API.
With your SiteB then, when the user logs in, you'd store their credentials in your $_SESSION or somewhere similar, and use them every time you make an API request.
RESTful Web Services Cookbook, by Subbu Allamaraju, is a great book I read recently that discusses this and other important RESTful API details.
By the way, it's not really RESTful unless your client is able to figure out the URLs all by itself just given an entry URL and knowledge of the media types the API features. Search for HATEOAS for more.
Also, if you try to do all this in JavaScript and SiteA and SiteB do not share the same domain, you may be forced to use JsonP to handle your API requests. While it can be done, IMHO it's undesirable. Use Zend_Http_Client in your SiteB's PHP code to do all the API communication. This way you get around the domain name issues. This also keeps your API hidden from prying eyes (right-click, view source!) and minimizes the potential for any cross-browser compatibility issues to interfere with your site's core functionality.
You can try to use a token, do the auth with JS get a token that you store in a Javascript variable and the use that token to do the "new" connection to the site A or site B.
Read also on OAuth for getting a idea on security issues. Not sure what your final objective is so... These are my 2 cents.
Best regards,