0

I am trying to display my widget on customer's website. But I cannot make it work using CORS in Laravel 5.5. Here is my code:

public/js/cb.js JavaScript file loaded on Customer's website.

window.onload = do_request();

function do_request()
{
    var url = "http://cb.dev.server-website.com/api/books/";
    var book_id = 0;
    var elementExists = document.getElementById("cb_script");
    if (typeof elementExists != "undefined" && elementExists) {
        var book_id = elementExists.getAttribute('data-id');
    }
    if (typeof book_id != "undefined" && book_id) {
        var parts = book_id.split('_');
        var loc = parts.pop();
        url += loc;
    }
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", url);
    xmlhttp.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
    xmlhttp.setRequestHeader("Access-Control-Allow-Origin", "*");
    xmlhttp.setRequestHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
    xmlhttp.setRequestHeader("Access-Control-Allow-Headers", "Content-Type");
    xmlhttp.setRequestHeader("Access-Control-Request-Headers", "X-Requested-With, accept, content-type");
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            var jsondata = JSON.parse(xmlhttp.responseText);
            if (
                typeof(jsondata) != "undefined" && jsondata != ""
                && typeof(jsondata['data']) != "undefined" && jsondata['data']
            ) {
                document.getElementById("stirbook_live").innerHTML = jsondata['data'].html;
                do_modal('myModal');
            }
        }
    };
    xmlhttp.send();
}

routes/api.php Laravel API Route file

Route::resource('books', 'Api\BooksapiController')
    ->middleware('preflight');

app/Http/Middleware/PreflightResponse.php Laravel Middleware file

namespace App\Http\Middleware;
use Closure;
class PreflightResponse
{
    public function handle($request, Closure $next)
    {
        if ($request->getMethod() === "OPTIONS") {
            return response('');
        }
        return $next($request);
    }
}

app/Http/Controllers/ApiBooksapiController.php Laravel Controller file

public function show($id)
{
    $data = $book_array = array();
    $output = '';
    $book = DB::table('books')
        ->join('questions', 'books.id', '=', 'questions.book_id')
        ->select('books.user_id', 'books.website', 'questions.id AS question_id', 'questions.question')
        ->get()->toArray();
    if (is_array($book) && count($book)) {
        $questions_html = '';
        for ($i = 0; $i < count($book); $i++) {
            $questions_html .= '<h5>Q: ' . $book[$i]->question . '</h5>';
            $answers = DB::table('answers')
                ->where([
                    ['question_id', $book[$i]->question_id],
                    ['status', 1]
                ])
                ->select('answers.id', 'answers.answer', 'answers.o_quantity', 'answers.o_percentage', 'answers.o_schedule', 'answers.o_overunder', 'answers.o_overunder')
                ->get()->toArray();
            if (is_array($answers) && count($answers)) {
                $questions_html .= '<ul style="list-style-type: disc;">';
                for ($j = 0; $j < count($answers); $j++) {
                    $questions_html .= '<ol>
                        <input type="radio" name="question_' . $book[$i]->question_id . '" value="1" id="answer_' . $j . '_' . $book[$i]->question_id . '" class="radio-class" onclick="do_survey(' . $book[$i]->question_id . ', ' . $answers[$j]->id . ', ' . $answers[$j]->o_quantity . ')" />&nbsp;
                        <label for="answer_' . $j . '_' . $book[$i]->question_id . '">' . $answers[$j]->answer . '</label>
                        <input type="hidden" name="offer_' . $j . '_' . $book[$i]->question_id . '" value="' . $answers[$j]->o_quantity . '" />
                    </ol>';
                }
                $questions_html .= '</ul>';
            }
        }
        $output .= '
            <!-- Trigger/Open The Modal -->
            <button id="myBtn" class="btn btn-primary">Opt-out</button>
            <!-- The Modal -->
            <div id="myModal" class="modal" style="display: none; position: fixed; z-index: 1; padding-top: 100px; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgb(0,0,0); background-color: rgba(0,0,0,0.4);">
              <!-- Modal content -->
              <div class="modal-content" style="background-color: #fefefe; margin: auto; padding: 20px; border: 1px solid #888; width: 80%;">
                <span class="close" style="color: #aaaaaa; float: right; font-size: 28px; font-weight: bold;">&times;</span>
                <div id="book_form">
                    <h3>book</h3>
                    <form name="fbook" action="#" method="post">
                    ' . $questions_html . '
                    </form>
                </div>
                <div id="offer_resp" style="display: none;">
                    <h3>Wait!!!</h3>
                    <div id="rm_body">
                        <p>Please don\'t go! How\'s this, we\'ll give you <span class="number-of-months"><numberofmonths></span> free month(s) for trying us out a little longer.</p>
                    </div>
                    <button id="yes_button" class="btn btn-primary" onclick="yes_response()" value="">Yes, give me <span class="number-of-months"><numberofmonths></span> month(s) free!</button>
                    <button id="no_button" class="btn btn-default" onclick="no_response()" value="">No thank you, I wish to cancel now!</button>
                </div>
              </div>
            </div>
        ';
    }
    if (isset($output) && $output) {
        $data['html'] = $output;
    }
    return response()->json(['data' => $data], 200);
}

But it is not working. When I try it in Postman it works fine. But when I try it on any other website it changes request from GET to OPTIONS and gives nothing. Could this be because my serving website is in sub-sub-domain? I have spent a whole day figuring out the solution and I have applied almost all solutions available on internet.

GET request changed into OPTIONS

aynber
  • 22,380
  • 8
  • 50
  • 63
Mohal
  • 581
  • 1
  • 8
  • 24
  • @nazim , I get the logic behind OPTIONS request and I tried to handle it in the Middleware but yet problem persists. – Mohal Jan 15 '18 at 10:09
  • changed the comment to an answer. You will need to refer to Laravel documentation to handle incoming OPTIONS request. – nazim Jan 15 '18 at 10:13
  • 1
    An `OPTIONS` request is a pre-flight request. The client asks the server if it allows CORS. The specific response **sent by the server** does not have an `allow-origin` header in the response so basically CORS is not allowed. You seem to have attempted to set the headers on the client which is not how it works. – apokryfos Jan 15 '18 at 10:13
  • @apokryfos , I have managed to add Access-Control-Allow-Origin: * in Response Header but still it is not working. – Mohal Jan 15 '18 at 10:45
  • Not working is too vague to go on. Is there any browser CORS error ? – apokryfos Jan 15 '18 at 10:58
  • @apokryfos , no Sir, no error! Just blank response! – Mohal Jan 15 '18 at 11:35
  • Still waiting for an answer with different idea. – Mohal Jan 16 '18 at 11:36

2 Answers2

0

The OPTIONS call is made as a pre-flight request by some browsers on their own, to check what kind of requests are acceptable (GET, POST, PUT...) at the requested URL.

Chrome sends OPTIONS request first when attempting CORS request.

nazim
  • 1,439
  • 2
  • 16
  • 26
-1

OK, so I have found the solution to the issue. The real problem was in the headers that I was sending with GET Request. It turns out that when sending a GET Request none of the Headers mentioned in public/js/cb.js are required. Just send a simple GET Request.

Here is the modified public/js/cb.js file code:

window.onload = do_request();

function do_request()
{
    var url = "http://cb.dev.server-website.com/api/books/";
    var book_id = 0;
    var elementExists = document.getElementById("cb_script");
    if (typeof elementExists != "undefined" && elementExists) {
        var book_id = elementExists.getAttribute('data-id');
    }
    if (typeof book_id != "undefined" && book_id) {
        var parts = book_id.split('_');
        var loc = parts.pop();
        url += loc;
    }
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", url);
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            var jsondata = JSON.parse(xmlhttp.responseText);
            if (
                typeof(jsondata) != "undefined" && jsondata != ""
                && typeof(jsondata['data']) != "undefined" && jsondata['data']
            ) {
                document.getElementById("stirbook_live").innerHTML = jsondata['data'].html;
                do_modal('myModal');
            }
        }
    };
    xmlhttp.send();
}

No change required in routes/api.php file but a small change is required in app/Http/Middleware/PreflightResponse.php file.

Modified code for app/Http/Middleware/PreflightResponse.php file:

namespace App\Http\Middleware;
use Closure;
class PreflightResponse
{
    public function handle($request, Closure $next)
    {
        if ($request->getMethod() === "OPTIONS") {
            return response('')
                ->header('Access-Control-Allow-Origin', '*')
                ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
        }
        return $next($request)
            ->header('Access-Control-Allow-Origin', '*')
            ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    }
}

All set here now. PreflightResponse Middleware will send a response header...

Access-Control-Allow-Origin: *

to customer's website. Telling it that the server accepts CORS Requests for each request to API.

Mohal
  • 581
  • 1
  • 8
  • 24