CodeIgniter 3 - set_header does not work - http-headers

This issue occurs when I'm trying to build CORS enabled RESTful API.
When I put this inside my view file (I'm using CodeIgniter)
// Allow from any origin
if (isset($_SERVER['HTTP_ORIGIN'])) {
$this->output->set_header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}", true);
$this->output->set_header('Access-Control-Allow-Credentials: true', true);
$this->output->set_header('Access-Control-Max-Age: 86400', true); // cache for 1 day
}
// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
$this->output->set_header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS", true);
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
$this->output->set_header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}", true);
}
$this->output->set_header("Content-type: text/x-json", true);
None of the code above that sets the header works. Even when I used PHP's own header() function.
I had to set the headers from .htaccess files to make CORS work.
What is the issue here? I'd like to be able to set the headers from CodeIgniter's view file.
Help.
PS : I'm using CI 3.1.2

please use something:
$response = (new \CI_Output())
->set_status_header(200)
->set_header('Content-Type: application/json')
->set_header('Access-Control-Allow-Origin: *')
->set_header('Access-Control-Max-Age: ');
$response->_display();
session_write_close();
exit;

Related

XMLHttpRequest blocked by CORS Policy on axios.post

The axios.post (code below) must send data to url api/add-todo, but I get these errors:
axios.post('http://localhost/vueoctober/todo/api/add-todo', todo).then(function (response) {
console.log(response);
}).catch(function(error) {
console.log(error);
});
The route api/add-todo is handled with October method Route::get() (https://octobercms.com/docs/services/router). Why is it not found?
If I change axios.post to axios.get it will be working! But I need post data, not get.
What I tried:
1) I tried to add these headers to .htaccess:
Header add Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTION"
It's working only for axios.get. The axios.post is still blocking.
2) I added Header set Access-Control-Allow-Origin "*" to httpd.conf.
Vue app is serving at port 8080, therefore axios.post url can't be relative.
I also stumbled and struggled with this on FF, even though I have this in the .htaccess:
Header set Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS".
After more searching I found a Gist by #marcomessa that fixed my issues.
https://gist.github.com/marcomessa/54d077a0208439f5340126ff629a3718
Look at the error message carefully, it says the response to the preflight request didn't have an HTTP ok status.
Clearly, your server-side code doesn't have a route handler for the OPTIONS request, so you need to add one.
As an aside, after the browser gets a successful OPTIONS response, it will make the POST request but you said:
The route api/add-todo is handled with October method Route::get()
You'll need to use Route::post() to handle that.
Hours of googling and I got answer...
1) Install plugin Cross-Origin Resource Sharing (CORS).
2) In htaccess of Vue app add:
Header set Access-Control-Allow-Origin '*'
Header set Access-Control-Allow-Headers "origin, x-requested-with, content-type"
Header set Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTION"
NOTICE! Write SET not ADD!
That's it.
So for clarification on this. There are always numerous ways to answer a problem. Here is what I did for mine. Check this for Preflight Request. The preflight request is created by the browser and is not for security. This function I created first will throw an okay message upon a request, then if the data contains any data it will then do what it is called (this is where you check for security). I don't have to mess with .htaccess files. Though I did install the CORS plugin because it is a nice plugin. Also the video from watch-learn does the author is making a cross-origin request in which he goes over how to correct the problem. I think he just filmed the video before preflight requests started to be a browser norm. Found routing information here.
Route::match(['POST', 'OPTIONS'],'api/update-todo', function(Request $req) {
$data = $req->input();
if (!empty($data)) {
Todo::where('id', $data['id'])
->update([
'name' => $data['name'],
'description' => $data['description'],
'status' => $data['status']
]);
return response()->json([
'Success' => $data,
]);
} else {
return response()->json([
'Success' => $req,
]);
}
});
I can not resolve it via axios, I wasted a lot of hours, but I resolved it very easy by this way.
Let's think we are posting:
{name:"Cynthia Merk", age:"22"}
I did the next function to send the last JSON (any JSON structure works):
const PostFunction = (data, letFunction, errorHandle) => {
let uri = "http://.../create.php";
let xhr = new XMLHttpRequest();
xhr.overrideMimeType("application/json");
xhr.open("POST", uri, true);
xhr.addEventListener("readystatechange", function() {
if(xhr.readyState === 4 && xhr.status === 200) {
letFunction(this.responseText);
}else{
errorHandle(this.responseText);
}
});
xhr.send(JSON.stringify(data));
}
You can invoke this function, it needs changes the "uri" variable value and it's required to use JSON.stringify to send the data.
In PHP the API is very easy too, for dummies:
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token, X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE");
header("Allow: POST, GET, OPTIONS, PUT, DELETE");
class DB extends PDO {
private $host = 'localhost';
private $dbname = 'zamuSysScheme';
private $user = 'root';
private $password = 'admin';
private $charset = 'utf8';
public function __construct(){
try{
$dns = 'mysql:host=' . $this->host . ';dbname=' . $this->dbname;
parent::__construct($dns, $this->user, $this->password, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
} catch(PDOException $e){
echo 'Error: ' . $e->getMessage();
exit;
}
}
}
$method = $_SERVER['REQUEST_METHOD'];
$pdo = new DB();
if($_SERVER['REQUEST_METHOD'] == 'POST'){
$json = file_get_contents('php://input');
$decoded = json_decode($json, true);
try{
$Name = $decoded["name"];
$Age = $decoded["age"];
if(!isset($Name)){
header("HTTP/1.1 402 FAIL");
echo "The paramenter Name is not present";
exit;
}
if(!isset($Age)){
header("HTTP/1.1 403 FAIL");
echo "The paramenter Age is not present";
exit;
}
$sqlStatement = "INSERT INTO Client (Name, Age) VALUES";
$sqlStatement .= "(:Name, :Age)";
$stmt = $pdo->prepare($sqlStatement);
$stmt->bindValue(':Name', $Name, PDO::PARAM_INT);
$stmt->bindValue(':Age', $Age, PDO::PARAM_INT);
$stmt->execute();
$Client_Id = $pdo->lastInsertId();
if($Client_Id){
header("HTTP/1.1 200 OK");
echo $Pago_Id;
exit;
}
}catch(Exception $except){
header("HTTP/1.1 400 FAIL");
echo "Error: " . $json . " /// " . $except;
exit;
}
}
header("HTTP/1.1 401 BAD REQUEST");
?>
I hope it can help you, any question is allowed and if I have the answer I'll glad to help.

Enable CORS in lumen

I have API developed using lumen. I can get request using postman. But when request using Jquery.ajax it is not working. So I need to know how to enable CORS in lumen API.
Consider creating a CorsMiddleware.php file with the following code. Find detail here.
<?php namespace App\Http\Middleware;
use Closure;
class CorsMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$headers = [
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => '86400',
'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With'
];
if ($request->isMethod('OPTIONS'))
{
return response()->json('{"method":"OPTIONS"}', 200, $headers);
}
$response = $next($request);
foreach($headers as $key => $value)
{
$response->header($key, $value);
}
return $response;
}
}
After saving it in your middleware folder, enable it by adding it to your bootstap/app.php file, on the list of you middleware like this
$app->middleware([
...
App\Http\Middleware\CorsMiddleware::class // Add this
]);
I hope it helps.
I'd recommend using the CORS package by Barry vd. Heuvel:
https://github.com/barryvdh/laravel-cors#lumen
It has configurable and supports Pre-flight request handling for ajax.
For Enable CORS policy inside Lumen you need to add a package via composer
Run the command for install cors package : composer require nordsoftware/lumen-cors
After that you need to configure service in bootstrap/app.php : $app->register('Nord\Lumen\Cors\CorsServiceProvider');
And Last one for middleware registration for application use :
$app->middleware([
'Nord\Lumen\Cors\CorsMiddleware', // top of all middleware
....... // rest of middlewares
]);
#The Oracle answer works properly to many, the problem is what surfaces as CORS problem might be something else. Please be informed that PHP errors in your code could emerge as CORS problem but it's actually not. Make use of different tools to troubleshoot if it's CORS or not.
For example to prove if it's CORS use postman, ex. GET method should work properly because postman is exempted from CORS as it's not a browser. Refer https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS for reference
You may sometimes drop your API url in the browser to check for such errors, if it's a backend error or specifically PHP error it normally displays/outputs on the browser with details like what caused the error and on which line etc. Enable Debugging in PHP if you think it's not enabled.

Symfony REST API authentication without sfGuardPlugin

I'm trying to find information on securing a HTTP REST API in a Symfony project, but all I can find is information about using sfGuardPlugin. From what I can see, this plugin isn't very useful for web services. It tries to have user profile models (which aren't always that simple) and have "sign in" and "sign out" pages, which obviously are pointless for a stateless REST API. It does a lot more than I'll ever have need for and I what to keep it simple.
I want to know where to implement my own authorisation method (loosely based on Amazon S3's approach). I know how I want the authorisation method to actually work, I just don't know where I can put code in my Symfony app so that it runs before every request is processed, and lets approved requests continue but unsuccessful requests return a 403.
Any ideas? I can't imagine this is hard, I just don't know where to start looking.
There is a plugin for RESTful authentication -> http://www.symfony-project.org/plugins/sfRestfulAuthenticationPlugin
Not used it though ....
How where you planning to authenticate users ?
The jobeet tutorial uses tokens ... http://www.symfony-project.org/jobeet/1_4/Doctrine/en/15
I ended up finding what I was looking for by digging into the code for sfHttpAuthPlugin. What I was looking for was a "Filter". Some details and an example is described in the Askeet sample project.
Stick a HTTP basicAuth script in your <appname>_dev.php (Symfony 1.4 =<) between the project configuration "require" and the configuration instance creation.
Test it on your dev. If it works, put the code in your index.php (the live equivalent of <appname>_dev.php) and push it live.
Quick and dirty but it works. You may want to protect that username/password in the script though.
e.g.
$realm = 'Restricted area';
//user => password
$users = array('username' => 'password');
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
die('Text to send if user hits Cancel button');
}
// || !isset($users[$data['username']]
// analyze the PHP_AUTH_DIGEST variable
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || !isset($users[$data['username']])) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
die('Wrong Credentials!');
}
// generate the valid response
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] != $valid_response) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
die('Wrong Credentials!');
}
// function to parse the http auth header
function http_digest_parse($txt)
{
// protect against missing data
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($needed_parts));
preg_match_all('#(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))#', $txt, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}
return $needed_parts ? false : $data;
}
// ****************************************************************************
// ok, valid username & password.. continue...

cross domain ajax file upload

I am using Valums Ajax Uploader (valums.com/ajax-upload) to upload files to a server. It works very well when the requesting URL and serving URL (ajax URL) are on same server but is not working when they are on different server.
Can someone please guide me about the same ?
This is a late post but hope it helps.
In your server-side (e.g php), add header defined like this:
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Max-Age: 1000');
header('Content-type: application/json');
if(array_key_exists('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', $_SERVER)) {
header('Access-Control-Allow-Headers: ' . $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
} else {
header('Access-Control-Allow-Headers: *');
}
if("OPTIONS" == $_SERVER['REQUEST_METHOD']) {
exit(0);
}

CAS authentication and redirects with jQuery AJAX

I've got an HTML page that needs to make requests to a CAS-protected (Central Authentication Service) web service using the jQuery AJAX functions. I've got the following code:
$.ajax({
type: "GET",
url: request,
dataType: "json",
complete: function(xmlHttp) {
console.log(xmlHttp);
alert(xmlHttp.status);
},
success: handleRedirects
});
The request variable can be either to the CAS server (https://cas.mydomain.com/login?service=myServiceURL) or directly to the service (which should then redirect back to CAS to get a service ticket). Firebug shows that the request is being made and that it comes back as a 302 redirect. However, the $.ajax() function isn't handling the redirect.
I wrote this function to work around this:
var handleRedirects = function(data, textStatus) {
console.log(data, textStatus);
if (data.redirect) {
console.log("Calling a redirect: " + data.redirect);
$.get(data.redirect, handleRedirects);
} else {
//function that handles the actual data processing
gotResponse(data);
}
};
However, even with this, the handleRedirects function never gets called, and the xmlHttp.status always returns 0. It also doesn't look like the cookies are getting sent with the cas.mydomain.com call. (See this question for a similar problem.)
Is this a problem with the AJAX calls not handling redirects, or is there more going on here than meets the eye?
There is indeed more going on than meets the eye.
After some investigation, it appears that jQuery AJAX requests made in this way fail if they're not made to the same subdomain. In this example, requests are being made to cas.mydomain.com from a different server. Even if it is also on mydomain.com, the request will fail because the subdomain doesn't match.
jQuery AJAX does handle redirects properly. I did some testing with scripts on the same subdomain to verify that. In addition, cookies are also passed as you would expect. See my blog post for this research.
Also keep in mind that the protocols must be the same. That is, since cas.mydomain.com is using HTTPS, the page from which you are calling it must also be on HTTPS or the request will fail.
Cross domain calls are not allowed by the browser. The simplest way would be to use JSONP on the mobile application end and use a CAS gateway to return a ticket.
You can make such cross-domain AJAX calls with a PHP proxy. In the following example the proxy is capable of calling REST web services that return a JSON string.
wsproxy.php
<?php
if (!isset($_POST["username"]) || !isset($_POST["password"]))
die("Username or password not set.");
$username = $_POST["username"];
$password = $_POST["password"];
if (!isset($_GET['url'])
die("URL was not set.");
//Rebuild URL (needed if the url passed as GET parameter
//also contains GET parameters
$url = $_GET['url'];
foreach ($_GET as $key => $value) {
if ($key != 'url') {
$url .= "&" . $key . "=" . $value;
}
}
//Set username and password for HTTP Basic Authentication
$context = stream_context_create(array(
'http' => array(
'header' => "Authorization: Basic " . base64_encode("$username:$password")
)
));
//Call WS
$json = file_get_contents($url, false, $context);
// Read HTTP Status
if(isset($http_response_header[0]))
list($version,$status_code,$msg) =
explode(' ',$http_response_header[0], 3);
// Check HTTP Status
if($status_code != 200) {
if($status_code == 404) {
die("404 - Not Found");
} else {
die($status_code . " - Error");
}
}
//Add content header
header('Content-Type: application/json');
print $json;
?>
URL usage
http://yourDomain.com/wsproxy.php?url=https://wsToCall.com/ws/resource?param1=false&param2=true
jQuery $.ajax or $.post
Note that if you don't need to pass username and password, then a GET request is sufficient.
$.ajax({
type : "POST",
url : "http://" + document.domain +
"/wsproxy.php?url=http://wsToCall.com/ws/resource?param1=false&param2=true",
dataType : "json",
success : handleRedirects,
data: { username: "foo", password: "bar" }
});