Zend RESTful Authentication - php

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,

Related

How to verify the origin domain of a request in PHP (Laravel)

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.

Twitter API responds with "Your credentials do not allow access to this resource" while calling statuses/update.json

I'm using Hybridauth 3 in my PHP app to make some periodical tweets on behalf of my account.
The app has all possible permissions. I'm giving it all permissions when it asks for them on the first auth step.
After that Twitter redirects me to the specified callback URL and there I'm getting a pair of access_token and access_token_secret.
But when I'm trying to make a tweet using these tokens - it gives me:
{"errors":[{"code":220,"message":"Your credentials do not allow access to this resource."}]}
Here's how I'm trying to make a tweet:
$config = [
'authentication_parameters' => [
//Location where to redirect users once they authenticate
'callback' => 'https://mysite/twittercallback/',
//Twitter application credentials
'keys' => [
'key' => 'xxx',
'secret' => 'yyy'
],
'authorize' => true
]
];
$adapter = new Hybridauth\Provider\Twitter($config['authentication_parameters']);
//Attempt to authenticate the user
$adapter->setAccessToken(/*tokens I've got from getAccessToken() on /twittercallback/*/);
if(! $adapter->isConnected()) {
// never goes here, so adapter is connected
return null;
}
try{
$response = $adapter->setUserStatus('Hello world!');
}
catch (\Exception $e) {
// here I've got the error
echo $e->getMessage();
return;
}
Tried to recreate tokens and key\secret pairs and passed auth process for the app many times, including entering password for my Twitter account (as suggested in some posts on stackoverflow) but still have this error.
P.S. According to this, Hybridauth has fixed the issue in the recent release.
It looks like you are using application authentication as opposed to user authentication. In order to post a tweet, you must authenticate as a user. Also, make sure your Twitter app has read/write privileges.
After comparing headers of outgoing requests from my server with the ones required by Twitter, I've noticed that Hybris doesn't add very important part of the header: oauth_token. At least it's not doing this in the code for Twitter adapter and for the scenario when you apply access token with setAccessToken(). It's just storing tokens in the inner storage but not initializing corresponding class member called consumerToken in OAuth1 class.
So to initialize the consumer token properly I've overridden the apiRequest method for Twitter class (before it used the defalut parent implementation) and added a small condition, so when consumer token is empty before the request - we need to try to init it.
public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [])
{
if(empty($this->consumerToken)) {
$this->initialize();
}
return parent::apiRequest($url, $method, $parameters, $headers);
}
I'm not sure that I've fixed it the best way, but as long as it's working - that's fine.
For your info setAccessToken was fixed in v3.0.0-beta.2 (see PR https://github.com/hybridauth/hybridauth/pull/880)
I faced the same error when implementing a sample app in clojure and the following resource was a huge help to sort out my confusion about application-only auth vs user authentication: https://developer.twitter.com/en/docs/basics/authentication/overview/oauth

Can Slim PHP make request to other services?

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

PHP OAuth fetch() under CakePHP

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).

Need complete example of authentication / authorization / access control in Zend Framework 1.9

I have some Zend Framework apps running and it's time to add user access restrictions. I am a firm believer that you should try to avoid rolling your own security infrastructure whenever possible and so I have been trying to figure out how to use Zend_Auth and Zend_Acl to implement it, so far without success.
I have searched all over the net, and at least one book, and I can't find an example of how to string all of the parts together. I found an example of authentication here, old examples of authorization / access control here and here, and proposals for the future here, here, and here, but I don't understand ZF well enough to put it all together in the present.
What I need is this: a simple public example or tutorial that completely details [as downloadable and runnable code] how to use the current Zend Framework release (1.9.5, no "proposals" or "laboratories") to manage the authentication/authorization/access control of three users (and their passwords) in three different roles (e.g. guest, member, administrator) to protect three different controllers in the default module. The example should use as much of the current ZF library as possible. And no, this isn't homework; the stakes are higher than that :-(
If it exists somewhere I haven't found it. Any help appreciated. IMO this would be very helpful for newcomers to ZF.
Disclosure: I have a community wiki question on ZF here beause I'm trying to figure out if I'll continue with it. But I really need to get my apps running now!
Pro Zend Framework Techniques, Chapter 8 has a nice treatment of this. Most of his approach is quite similar to what I use, with the exception of the preDispatch method. When authenticating I have preDispatch redirect instead of silently dispatching to another controller. I also preserve the Url that was requested for the use of the login action.
class SitePluginAuth extends Zend_Controller_Plugin_Abstract
{
private $_auth;
private $_acl;
private $_noauthPath = '/account/log-in/';
private $_noacl = array('module' => 'default', 'controller' => 'error', 'action' => 'denied');
public function __construct($auth, $acl)
{
$this->_auth = $auth;
$this->_acl = $acl;
}
public function preDispatch($request)
{
$resource = $request->controller;
if (!$this->_acl->has($resource)) return;
$controller = $request->controller;
$action = $request->action;
$module = $request->module;
if ($this->_auth->hasIdentity()) {
$identity = $this->_auth->getIdentity();
$role = 'member';
}
else {
$role = 'guest';
}
/*
* Remember to URL encode the parameter value. Also, when you are processing the value of the
* redirect URL, make sure that it is a URL on your site or a relative URL to avoid any security
* attacks like a phishing scheme. Otherwise, a third party can target your site's login page and
* then redirect back to their site and might have access to the user's secured session.
*
* The reason I don't use the session to store the URL, is that search engine spiders can end up
* creating sessions as they hit links on your site that are secured and require login. Since they
* have no credentials, the session is created only to timeout 30 minutes later.
*/
if (!$this->_acl->isAllowed($role, $resource, $action)) {
if (!$this->_auth->hasIdentity()) {
$requestedUrl = substr($request->getRequestUri(), strlen($request->getBaseUrl())); // relative url
$loginUrl = $this->_noauthPath.'?requestedUrl='.urlencode($requestedUrl);
$redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
$redirector->gotoUrl($loginUrl);
}
else {
$request->setModuleName($this->_noacl['module']);
$request->setControllerName($this->_noacl['controller']);
$request->setActionName($this->_noacl['action']);
}
}
}
}

Categories