0

CORS(Cross-Origin Resouce Sharing) - một khái niệm mà web developer cần phải biết.

1. Giới thiệu

Một nhu cầu rất thông dụng với các developer web đó là truy truy vấn qua API. Tuy nhiên, khi thực hiện request và xử lý dữ liệu từ API nhiều khi chúng ta lại gặp những vấn đề khó khăn, đôi khi, bạn sẽ thấy những lỗi như thế này: Đây là lỗi liên liên quan đến khái niệm CORS, rất nhiều lập trình viên phải đối mặt với các vấn đề liên quan đến CORS. Vậy CORS là gì? Và lý do tại sao chúng ta cần CORS?

2. CORS là gì?

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own.(source: mozilla)

Ở trên là định nghĩa về CORS được trích dẫn từ document của Mozilla, có thể nói ngắn gọn, CORS(Cross-Origin Resource Sharing) là một cơ chế bảo mật cho phép một web page từ một domain hoặc origin truy cập vào resource của một domain khác.

3. Tại sao chúng ta cần CORS?

CORS được sinh ra là vì same-origin policy, là một chính sách liên quan đến bảo mật được cài đặt vào toàn bộ các trình duyệt hiện nay. Chính sách này ngăn chặn việc truy cập tài nguyên của các domain khác một cách vô tội vạ. Nếu không có các tính năng như CORS, các websites sẽ không có quyền truy cập tới các resources từ origin khác.

Origin là kết hợp của bộ ba: scheme (protocol), host (domain) và port.

Same-origin là hai đối tượng có cùng same-origin khi chúng có cùng scheme, host và port.

Ta có một số ví dụ kịch bản như sau để giúp hiểu rõ hơn về CORS và các khái niệm liên quan:

Ví dụ https://examplebank.comhttps://examplebank.com/api được được xem là cùng origin, nhưng https://examplebank.com http://examplebank.com là 2 origin khác nhau (vì khác protocol).

Để hiểu rõ hơn, chúng ta sẽ xem xét ví dụ sau đây:

So sánh origin với URL: http://store.company.com/dir/page.html

URL KẾT QUẢ NGUYÊN NHÂN
http://store.company.com/dir2/other.html Same origin Only the path differs
http://store.company.com/dir/inner/another.html Same origin Only the path differs
https://store.company.com/page.html Failure Different protocol
http://store.company.com:81/dir/page.html Failure Different port (http:// is port 80 by default)
http://news.company.com/dir/page.html Failure Different host

Đối với các path hay query parameters thì không bị tác động bởi cơ chế same-origin policy.

Ví dụ tiếp theo là về nguy cơ về cross-domain mà same-origin có vai trò hạn chế rủi ro cho website:

Giả sử chúng ta đã đăng nhập vào một tài khoản bank tại: https://examplebank.com, trong khi đó, ở 1 tab khác của browser bạn vô tình click vào 1 trang web chứa mã độc: https://evilunicorns.com.

Nếu không có cơ chế same-origin, Nó có thể đã thực hiện các đoạn scripts và thực hiện request(được xác thực) tới https://examplebank.com/api và thực hiện các hành vi nguy hiểm, mặc dù web đó không có quyền truy cập trực tiếp vào cookie của domain. Để đảm bảo an toàn, chỉ có https://examplebank.com mới có quyền thực hiện các requests tới https://examplebank.com/api

Bằng cách hạn chế các HTTP requests đến cùng một origin, same-origin policy sẽ hạn chế một số kịch bản như trên và một số lỗ hổng liên quan đến Cross-Site Request Forgery (CSRF).

6. Các truy vấn dùng CORS

Các truy vấn sau bắt buộc phải sử dụng CORS, theo tiêu chuẩn quốc tế.

  • Các truy vấn bằng XMLHttpRequest hoặc Fetch API đến một domain khác.
  • WebGL Texture
  • Ảnh, video được vẽ vào canvas sử dụng drawImage.
  • Web fonts truy vấn đến domain khác qua @fontface của CSS, trong đó trang web chỉ có thể sử dụng font dạng True Type nếu được cho phép.

7. CORS hoạt động như thế nào?

Có hai kiểu CORS requests là Simple requestsPreflighted requests

7.1 Simple request

Một truy vấn CORS đơn giản như đã nói ở trên, có thể có gói tin HTTP dạng như sau:

GET /cors HTTP/1.1
Origin: https://api.topdevvn.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/...

Với các phương thức khác, gói tin HTTP cũng tương tự như vậy. Lưu ý rằng, một truy vấn CORS hợp lệ luôn luôn có Origin ở trong header. Giá trị của header này hoàn toàn được thiết lập tự động bởi trình duyệt, và không ai có thể thay đổi nó được. Giá trị của header này sẽ bao gồm scheme (http), domain (api.bob.com) và cổng (trong trường hợp dùng cổng mặc định thì không cần, ví dụ http dùng cổng 80). Giá trị của header chính là biểu thị nguồn gốc của truy vấn.

Một điểm lưu ý nữa là sự xuất hiện của header Origin không đồng nghĩa với việc truy vấn đó là cross origin. Dù tất cả các truy vấn cross origin đều có header này, nhưng một số truy vấn same origin cũng có header này. Điều đó phụ thuộc vào từng trình duyệt cụ thể.

Ví dụ:

  1. Ở một tab của browser, chúng ra truy cập: https://www.mydomain.com và nó thực hiện request đến: GET https://api.mydomain.com/widgets

  2. Browser sẽ tự động thêm Origin Request Header cho cross-origin requests:

GET /widgets/ HTTP/1.1
Host: api.mydomain.com
Origin: https://www.mydomain.com
[Rest of request...]
  1. Server sẽ kiểm tra Origin request header. Nếu giá trị của Origin được cho phép, Server trả về header Access-Control-Allow-Origin với giá trị của request header Origin.
HTTP/1.1 200 OK  
Access-Control-Allow-Origin: https://www.mydomain.com  
Content-Type: application/json
[Rest of response...]
  1. Khi browser nhận được phản hồi, nó sẽ kiểm tra liệu Access-Control-Allow-Origin có khớp với origin của chính tab đó không. Nếu không, phản hồi sẽ bị chặn. Nếu hợp lệ, giá trị Access-Control-Allow-Origin khớp chính xác với origin hoặc là ký hiệu * .

Access-Control-Allow-Origin: <origin> | *

Như bạn có thể thấy, Server có quyền kiểm soát xem có cho phép request hay không tùy thuộc vào origin của request. Browser đảm bảo việc đặt Origin request header chính xác trước khi gửi.

7.2 Preflighted request

Không phải truy vấn nào cũng là đơn giản do việc trao đổi dữ liệu giữa trình duyệt và máy chủ diễn ra rất đa dạng. Các phương thức PUT hay DELETE cũng thường xuyên được sử dụng. Ngoài ra kiểu dữ liệu JSON (Content-Type: application/json) cũng là lựa chọn của nhiều lập trình viên. Trong những trường hợp như vậy, trước khi truy vấn chính được thực hiện thì một truy vấn gọi là preflight sẽ được gửi đi trước.

Ở phía frontend, các truy vấn đơn giản hay phức tạp đều trông sẽ giống nhau. Truy vấn preflight hoàn toàn được thực hiện ngầm và trong suốt với người dùng. Truy vấn preflight sẽ được gửi đi trước nhằm xác định xem truy vấn thực sự có thể thực hiện được hay không.

Sau khi có được phản hồi tích cực, trình duyệt sẽ gửi truy vấn thực sự. Kết quả của truy vấn preflight có thể được cache nên nó không cần phải thực hiện cho mọi truy vấn.

Preflighted request là một kiểu CORS request khác, với những request có tác động tới data như POST, PUT, DELETE,... Browser sẽ gửi một request với OPTIONS method tới resource của domain trước khi gửi request chính, nhằm để xác định xem request chính có được server cho phép hay không? Lúc này chúng ta sẽ thấy có 2 request tới cùng một Domain.

Nếu hợp lệ Server sẽ trả về response kèm một số header như: Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Max-Age,...

Trong đó:
* Access-Control-Allow-Methods: Mô tả những method nào client có thể gửi đi.
* Access-Control-Max-Age: Mô tả thời gian hợp lệ của preflight request

Preflighted request được thực hiện khi:

  • Sử dụng các method khác: GET, HEAD hoặc POST. Ngoài ra, nếu dùng POST để gửi request data với Content-Type khác với: application/x-www-form-urlencoded, multipart/form-data, text/plain. Ví dụ: nếu POST request gửi một XML payload đến server bằng cách sử dụng application/xml hoặc text/xml, thì sẽ thực hiện preflighted request.

  • Đặt các custom headers trong request (ví dụ: request sử dụng một header , chẳng hạn như X-PINGOTHER)

Ví dụ:

  1. Ở một tab của browser, chúng ta truy cập https://www.mydomain.com , đồng thời nó sẽ gửi một request đến: POST https://api.mydomain.com/widgets. Browser đầu tiên sẽ gửi OPTIONS request (preflight request) đến Server với Requested Method và Requested Headers giả định của request chính. Trong đó, các headers: Access-Control-Request-Method và Access-Control-Request-Headers sẽ được browser thêm tự động.
OPTIONS /widgets/ HTTP/1.1
Host: api.mydomain.com
Origin: https://www.mydomain.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type
[Rest of request...]
  1. Server phản hồi lại các HTTP methods và headers được cho phép.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.mydomain.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Content-Type: application/json
[Rest of response...]  
  1. Nếu request chính được cho phép, browser sẽ gửi CORS request(simple request), các bước tiếp theo sẽ được hiện giống như simple request.
POST /widgets/ HTTP/1.1
Host: api.mydomain.com
Authorization: 1234567
Content-Type: application/json
Origin: https://www.mydomain.com
[Rest of request...]
  1. Phản hồi chứa giá trị origin trong Access-Control-Allow-Origin , browser lúc này sẽ thực hiện xử lý và gửi các request tiếp theo.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.mydomain.com
Content-Type: application/json
[Rest of response...]

8. Cấu hình CORS dùng Nginx

#
# Wide-open CORS config for nginx
#
location / {
     if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        #
        # Custom headers and headers various browsers *should* be OK with but aren't
        #
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        #
        # Tell client that this pre-flight info is valid for 20 days
        #
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     if ($request_method = 'POST') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
     }
     if ($request_method = 'GET') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
     }
}

9. Hỗ trợ các framework

Laravel CORS

Khi chúng ta code vài ứng dụng dưới local mà có connect tới Laravel backed, thì bạn sẽ nhận cái thông báo error CORS ngay. Vì vậy cần tạo một middleware sau:

php artisan make:middleware Cors

Sau đó update header trong app/Http/Middleware/Cors.php

<?php
namespace App\Http\Middleware;
use Closure;
class Cors
{
  public function handle($request, Closure $next)
  {
    return $next($request)
      ->header(‘Access-Control-Allow-Origin’, ‘*’)
      ->header(‘Access-Control-Allow-Methods’, ‘GET, POST, PUT, DELETE, OPTIONS’)
      ->header(‘Access-Control-Allow-Headers’, ‘X-Requested-With, Content-Type, X-Token-Auth, Authorization’);
  }
}

Sau đó, đăng ký middleware trong app/Http/kernel.php

protected $routeMiddleware = [
  ‘auth’ => \App\Http\Middleware\Authenticate::class,
  ‘auth.basic’ => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
  ‘guest’ => \App\Http\Middleware\RedirectIfAuthenticated::class,
  ‘cors’ => \App\Http\Middleware\Cors::class, // <-- thêm vào hàng này
 ];

Nếu bạn muốn bật CORS bất kỳ route nào, chỉ cần add middleware này trong route registration. Ngoài ra bạn có thể dùng packgage ngoài tại barryvdh/laravel-cors.

CORS Nodejs

Chúng ta sử dụng dòng code dưới đây để set một header trên response của bạn để bật CORS:

res.header("Access-Control-Allow-Origin", "*");

Tiếp theo bật CORS cho toàn bộ resource trên server

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

Chỉ định file nào đó

app.get('/file', function(req, res){
  var file = __dirname + '/file.zip';
  res.download(file); // Set disposition and send it.
});

Ví dụ đầy đủ:

var express = require('express');
var app = express();
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});
app.get('/', function (req, res) {
  var data = {
    "SmartPhone": [
      "iPhone",
      "Samsung"    ]
  };
  res.json(data);
});
app.get('/file', function(req, res){
  var file = __dirname + '/file.zip';
  res.download(file); // Set disposition and send it.
});

Ở đây mình chỉ ví dụ cho 2 framework là Laravel và NodeJs, ngoài ra còn nhiều framework khác được hỗ trợ và các bạn có thể tìm hiểu thêm nhé.

10. Kết luận

Trên đây là tất cả những gì cơ bản để bạn biết CORS là gì, cách nó giúp các ứng dụng web dễ dàng hơn trong việc trao đổi thông tin cũng như hiển thị nội dung và cơ chế bảo mật của các khái niệm liên quan trong bài viết. Hy vọng bài viết này sẽ giúp ích cho bạn khi tìm hiểu về CORS, bài viết dĩ nhiên sẽ có những thiếu sót, nếu có gì góp ý các bạn hãy comment bên dưới nhé.

Tham khảo https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy https://www.moesif.com/blog/technical/cors/Authoritative-Guide-to-CORS-Cross-Origin-Resource-Sharing-for-REST-APIs/ https://techblog.vn/cors-la-gi


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí