My Profile Photo

MSc. Alban Afmeti


Information Technology is my passion, especially software development. I am trying to grow professionally every day with hard work, with practice and online courses. My objective is clear. Everything is possible if we devote to that. All the persons with a higher position than me are there because the life has bring to them some opportunities, not because they are smarter than me. Let's find these opportunities and let's thank the god that we have a good healthy life.


CORS problem solution between a Laravel API and Vue.js client

If you have been developing a modern web-based application, chances are you have been confronted by this error message in your browser’s console:

XMLHttpRequest cannot load http://domain.com/api/get. 
Response to preflight request doesn't pass access control check: 
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'http://another-domain.com' is therefore not allowed access.

Every time we need to integrate a web app with some external service or some server-side API we can bump into this error. We are seeing this error because we are violating something called the Same-Origin Policy (SOP). This is a security measure implemented in browsers to restrict interaction between documents (or scripts) that have different origins. The origin of a page is defined by its protocol, host and port number.

We are going to show how to implement a solution of this problem using a Laravel API in server-side and a Vue.js front-end application in the client-side. We need to create two FTP accounts in the server, one for the client-side and another for the server-side so we can host our projects with different domains. The projects need to be in different domains or ports to happen a such error. You can use any free hosting service, or your localhost with different ports, but here I am using my VPS server, so my domains are client.techalin.com for the Vue.js 2.0 project and server.techalin.com for the Laravel 5.3 project. This also can be done using Homestead to easily host two domains.

Our Projects

Server-Side

So initially we install the Laravel Project:

composer create-project --prefer-dist laravel/laravel server_root

In the Laravel project initially we only create a test route, where we return some JSON data. This route will be used from the client-side to make an API request, so we will create it inside routes/api.php file.

<?php

use Illuminate\Http\Request;

Route::get('/testing', function () {
    return response()->json(['name' => 'Alban Afmeti', 'state' => 'AL']);
});

We have made this route accessible to the public, so if we paste to the browser’s address bar this url http://server.techalin.com/api/testing and press Enter, we will see the following output:

{"name":"Alban Afmeti","state":"AL"}

Client-Side

We need to install vue-cli a command line interface for easy working with Vue apps. We can install it using npm. You need to install npm first so if you never heard about it, see the documentations in the link. After installing npm, run in your terminal the following command:

npm install -g vue-cli

Then we run vue init webpack client_root and the client project will be installed after completing some configuration info during the installation.

Inside the root directory of client project run: npm install to install the dependencies of the project.

To see the Vue project in our browser we have to run: npm run dev while we are in development mode, or npm run build for the production mode. Should note that, in the production mode the public directory for the project will be dist directory.
Go to the url of client project in your browser, in my case client.techalin.com and we will see Vue.js welcome page:

Vue Welcome Page

After installing the projects, we need to make any HTTP request from client to server to see the CORS problem, then we need to find a solution for it.

Write some code for the Vue project. We need to make some changes to the file App.vue located under the src directory. App.vue has some sections, and you need to see the documentation here to learn more about them. The content of my Vue file is:

<template>
    <div id="app">
        <img src="./assets/logo.png">
        <hello></hello>
    </div>
</template>

<script>
    import Hello from './components/Hello'

    export default {
        name: 'app',
        components: {
            Hello
        },
        created () {
            this.$http.get("http://server.techalin.com/api/testing")
                    .then(response => {
                        console.log(response)
            })
        }
    }
</script>

<style>
    #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
    }
</style>

I am using the same code, not making many changes only I have added created () section inside export default {...} of the <script>. This function is called synchronously before the compilation starts. At this stage, instance properties like $el, $data are available, but the DOM is in a pre-compile state, and the data has not been observed yet. Usually the created hook is used to attach additional initial states to the ViewModel. Any non-function properties attached to the ViewModel in the created hook will be copied to the data object and observed later. For more about these options see the documentation for the Instantiation Options.

Inside this section, I have put some code making a HTTP request to the url http://server.techalin.com/api/testing
js this.$http.get("http://server.techalin.com/api/testing") .then(response => { console.log(response) })
So after making the request I print the response to the browser’s console.
We need to run the app to see the changes in the browser so we run npm run dev.
Go to the browser’s console and you are going to see the following error:

CORS Error

This is a CORS problem and we need to find a solution for it.

CORS Problem Solution

What we are going to do is to create a middleware to handle all the incoming requests. This middleware is going to add some headers to the request to allow the communication with some trustful domains. We need to declare some trustful domains which we are going to save to an sqlite database. The list of domains will contain our client domain, in my case http://client.techalin.com

Go to the Laravel project, and create an sqlite file inside the database directory called db.sqlite. In the .env environment file set DB_CONNECTION=sqlite and clear all other information about the database connection. Inside the config/database.php file set the sqlite database to db.sqlite like the code below:

<?php

'sqlite' => [
    'driver' => 'sqlite',
    'database' => env('DB_DATABASE', database_path('db.sqlite')),
    'prefix' => '',
],

Create a table called domains where we are going to save all the trusted domains which can use our Laravel API. Let’s use migrations, so in the terminal run the command: php artisan make:migration create_domains_table and a new file will be generated to the database/migrations directory. Write this content to it:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateDomainsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('domains', function (Blueprint $table) {
            $table->increments('id');
            $table->string('domain')->unique();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('domains');
    }
}

Then we run the command php artisan migrate to create the table and if we browse the db.sqlite file with an sqlite browser, the table domains is created. Create a model related to the current table called Domain running in the terminal the command: php artisan make:model Domain.

For convenience, create a seeder to populate this table so run in terminal php artisan make:seeder DomainsTableSeeder and put the following content:

<?php

use Illuminate\Database\Seeder;
use App\Domain;

class DomainsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $domains = ["http://client.techalin.com", "http://client2.domain.com"];

        foreach ($domains as $domain) {
            Domain::create(["domain" => $domain]);
        }
    }
}

In the $domains array we put our client domains that are going to access our Laravel API. After creating the seeder run php artisan db:seed --class=DomainsTableSeeder to write the data to database. We will see our table populated as below:

Domains Table

Now we are ready to create our middleware which are going to name Cors. If we run in the terminal the command php artisan make:middleware Cors a new file will be generated to app/Http/Middleware called Cors.php which is a PHP class containing a function handle() and here we are going to put our code.

The content of the middleware will be as the following:

<?php

namespace App\Http\Middleware;

use Closure;
use App\Domain;

class Cors
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if($request->server('HTTP_ORIGIN')) {
            $origin = $request->server('HTTP_ORIGIN');
            
            $domain = Domain::where("domain", $origin)->get();
            if($domain) {
                header('Access-Control-Allow-Origin: ' . $origin);
                header('Access-Control-Allow-Headers: Origin, Content-Type');
            }
        }

        return $next($request);
    }
}

We need to know the origin of the incoming request so we check if it is set and we save it in a variable $origin = $request->server('HTTP_ORIGIN'). Then we check if $origin exists in the database. If it exists there means that it’s a trusted domain so we add two headers to our request. These two headers allow the request to be completed.

<?php

header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Headers: Origin, Content-Type');

In the first line we have set the header Access-Control-Allow-Origin: $origin which tells the browser that the content of this page is accessible to the current $origin.
Access-Control-Allow-Headers is a comma separated list of acceptable headers where we have put Origin, Content-Type,. In this case Origin header indicates the origin of the cross-site access request or preflight request. Content-Type allows the headers which show what content type is the requested data.
This was our middleware and we need to register it to the Kernel so go to Kernel.php located under the app/Http directory. In the property $middleware we add an array element like below:

<?php

/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \App\Http\Middleware\Cors::class //added by us
];

Every request will pass through the middleware we just created. After saving everything, we need to test it so go to client application to the browser, in my case http://client.techalin.com and click Enter. We are going to see the following output:

Cors Success

So the response status was 200 and the data is returned correctly. response.body contains { name : "Alban Afmeti", state : "AL"} meaning that the CORS problem was solved.

In this example, I have used Laravel and Vue.js apps to show you the solution, but this can be done with different types of apps too. For security reasons, browsers restrict cross-origin HTTP requests (HTTP requests between different origins), and what I did was adding a new header Access-Control-Allow-Origin: $origin to the request made from client-side to server-side. This header contains the trusted origin where we are making the request from. So this solution can be used in different frameworks or apps and not only in Laravel.

Conclusion

In this article, I explained a method on how to solve CORS (Cross-Origin Resource Sharing) problem when we create applications with different domains which comunicate each other with API. In the server-side we built a project using Laravel Framework 5.3 where we made API requests, and in the client side (Front End) we used a Vue.js 2.0 application to make HTTP requests. We registered a middleware for every incoming request, which checks if the request is part of the trusted domains saved in an sqlite database. If it is, we add to it some necessary headers, which let the request to be successfully completed and to get a successful response.

comments powered by Disqus