contributte / apitte

:wrench: An opinionated and enjoyable API framework based on Nette Framework. Supporting content negotiation, debugging, middlewares, attributes, annotations and loving openapi/swagger.

Home Page:https://contributte.org/packages/contributte/apitte/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

how to CORS?

vylink opened this issue · comments

Hi Guys,

many thanks for your fantastic work!

I'm just wandering, what is the best approach to make "Apitte" available for Ajax POST. Not sure, how to handle responses for "OPTIONS" requests. Any idea? Any documentation? It will be maybe possible using some middleware, but I have no clue how to implement it.

You would like to sent headers like Access-Control-Allow-Origin: * with all responses, right?
I would prefer a response decorator, middlewares are used too soon. Also endpoint could return new response so you cannot rely on modifications of response through middlewares if you don't return response immediatelly from that middleware.

return $response->withHeader('Access-Control-Allow-Origin', '*');

services:
    decorator.response.cors:
        class: App\Api\Response\ResponseCorsDecorator
        tags: [apitte.core.decorator: [priority: 60, type: handler.after]]

@f3l1x PSR-15 middlewares start to seem look good for me. If you want return response with PSR-15 then you need create your own. It would prevent bad usage as modification of response and passing it to endpoint is useless when negotiations are used.

Many thanks for quick answer. Why is this not a part of APITTE? I think, this is very common use case to use it with AJAX. It will be nice to have an option to add this option using config file. Something like this: https://github.com/tomaj/nette-api#javascript-ajax-calls-cors---preflight-options-calls

CORS headers specifically - many ways to implement, easy to use it bad and noone needed it yet as apitte is usually not used with js-only frontend.

Decorators are flexible and could handle all needed modifications, so I don't see reason why we should solve such a specific usecase.

Thanks for that. I'm nearly there.

I've Added following decorator and "OPTIONS" requests seems to be working now:

class CorsDecorator implements IDecorator
{
    /**
     * @param ServerRequestInterface|ApiRequest $request
     * @param ResponseInterface $response
     * @param mixed[] $context
     * @return ResponseInterface
     */
    public function decorate(ServerRequestInterface $request, ResponseInterface $response, array $context = [])
    {
        return $response
            ->withHeader('Access-Control-Allow-Origin', '*')
            ->withHeader('Access-Control-Allow-Credentials', 'true')
            ->withHeader('Access-Control-Allow-Methods', 'GET', 'POST, OPTIONS')
            ->withHeader('Access-Control-Allow-Headers', 'Accept, Overwrite, Destination, Content-Type, Depth, User-Agent, Translate, Range, Content-Range, Timeout, X-Requested-With, If-Modified-Since, Cache-Control, Location')
            ->withHeader('Access-Control-Max-Age', 1728000);
    }
}

... however, I have to add "OPTIONS" method to every controller's method and return "request" when the "OPTIONS" is called. Is there any other option how to handle it globally?

    /**
     * @Path("/test")
     * @Method({"POST", "OPTIONS"})
     */
    public function index(ApiRequest $request, ApiResponse $response)
    {
        if ($request->getMethod() === "OPTIONS") {
            return $response;
        }
        // rest of the code to handle POST REQUEST
    }

If the "OPTIONS" value missing in the "@method", following error is returned:

{
    "status": "error",
    "data": {
        "code": 405,
        "error": "Method \"OPTIONS\" is not allowed for endpoint \"/api/v1/test\"."
    }
}

Why would you send unmodified, empty response from each endpoint? Your example just returns empty response which allows request to be send from any website.

Just trying to make it works with axios. But still no luck. Already spent couple of days with "apitte"and I would like to make it works and use it as a main api platform for future project. I've sent you an email and also skype message. Will you be available for 1 hour paid support?

commented

Hi, I have the same CORS problem with Apitte. I could be simple but it is not. I have JQuery ajax request which make 2 requests. First one is OPTIONS the second is POST or GET. The JQuery code looks like:

$.ajax(url, {
                    headers: {
                        'access-control-allow-origin': '*'
                    },
                    method: 'POST',
                    data: JSON.stringify(data),
                    cache: false,
                    contentType: 'application/json; charset=utf-8',  // request
                    dataType: 'application/pdf',  // response
                    crossDomain: true,

                    success: function(data) {
                        console.log('result data');
                        ...
                    },
                    error: function (data) {
                        ...
                    }
                });

The APitte code is here:

  /**
   * @Path("/{id}/export-pdf")
   * @Method({"GET", "OPTIONS", "POST"})
   * @RequestParameters({
   * 		@RequestParameter(name="id", type="string", required=true, in="path")
   * })
   */
  public function exportToPdf(ApiRequest $request, ApiResponse $response)
  {
    Debugger::log('+++++++++++++++++++++++++++++++++++++++++');
    Debugger::log($request->getMethod());
    Debugger::log($response->getHeaders());
    if( $request->getMethod() == "OPTIONS" ) return $response->withHeader('Access-Control-Allow-Origin', '*')->withStatus(200);
    Debugger::log('+++++++++++++++++++++++++++++++++++++++++');  // This is OK

    .....

    $response = $response->withHeader('Access-Control-Allow-Origin', '*');
    $response = FileResponseAdjuster::adjust($response, $stream, $fileName, 'application/pdf', true);

    return $response->withStatus(200);
  }

OPTIONS request works file but POST/GET does not work although I set $response->withHeader('Access-Control-Allow-Origin', '*'); What am I doing wrong? Dont understand it.

Please help me, thanks for help.

If OPTIONS request is sent, you have to respond with $response->withHeader('Access-Control-Allow-Origin', '*').

commented

You mean without withStatus(200)?

commented

I am still getting an error:

Access to XMLHttpRequest at 'http://localhost:8080/api/v1/campaigns/2542/export-pdf' from origin 'http://lurity.crm.localhost' has been blocked by CORS policy: Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response.
jquery.min.js:2          POST http://localhost:8080/api/v1/campaigns/2542/export-pdf net::ERR_FAILED

OPTIONS request comunication looks like:

RESPONSE
access-control-allow-origin: *
cache-control: no-store, no-cache, must-revalidate
connection: close
content-length: 0
content-type: text/html; charset=utf-8
date: Wed, 15 Jun 2022 08:23:15 GMT
expires: Thu, 19 Nov 1981 08:52:00 GMT
pragma: no-cache
server: Apache/2.4.46 (Win64) PHP/7.4.9
set-cookie: nette-samesite=1; path=/; HttpOnly; SameSite=Strict
set-cookie: PHPSESSID=i0f86jvdgjgf6cjlp57ku03fq8; expires=Thu, 16-Jun-2022 08:23:15 GMT; Max-Age=86400; path=/; HttpOnly
x-frame-options: SAMEORIGIN
x-powered-by: Nette Framework 3

REQUEST
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: sk-SK,sk;q=0.9,cs;q=0.8,en-US;q=0.7,en;q=0.6
Access-Control-Request-Headers: access-control-allow-origin,content-type
Access-Control-Request-Method: POST
Connection: keep-alive
Host: localhost:8080
Origin: http://lurity.crm.localhost
Referer: http://lurity.crm.localhost/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36

Exactly the same problem as @camohub and many others before. In general it is clear that the problem will be linked to the every main frontend JS framework (vue, react, angular, ...) in the context of REST API. I also tried to prepare a special response decorator for CORS, but without success.

@f3l1x Would you be so kind as to provide a concrete example of how to proceed correctly (or elaborate this case in more detail in the documentation)? Unfortunately I'm out of ideas, totally no clue where the error might be. I am sure it would help more people. Thank you very much in advance!

Hi guys. I will look and provide better example. I am on vacation at this moment, first week at July will be at home. Sorry for troubles.

commented

Still on vacation?

Hi @camohub @archervalo @vylink.

I've added example of CORS middleware (contributte/apitte-skeleton@4b21566).

Working JSFiddle (https://jsfiddle.net/rtz4wqbj/)

➜ curl -XOPTIONS -i https://examples.contributte.org/apitte-skeleton/api/v1/static/text?_access_token=admin
HTTP/2 200
date: Tue, 26 Jul 2022 13:00:15 GMT
server: Apache/2.4.38 (Debian)
x-powered-by: Nette Framework 3
x-frame-options: SAMEORIGIN
access-control-allow-origin: *
access-control-allow-credentials: true
access-control-allow-methods: *
access-control-allow-headers: *
set-cookie: _nss=1; path=/; secure; HttpOnly; SameSite=Strict
x-content-type-options: nosniff
x-xss-protection: 1;mode=block
content-length: 0
content-type: text/html; charset=utf-8
➜ curl -XGET -i https://examples.contributte.org/apitte-skeleton/api/v1/static/text?_access_token=admin
HTTP/2 200
date: Tue, 26 Jul 2022 13:00:22 GMT
server: Apache/2.4.38 (Debian)
x-powered-by: Nette Framework 3
x-frame-options: SAMEORIGIN
access-control-allow-origin: *
access-control-allow-credentials: true
access-control-allow-methods: *
access-control-allow-headers: *
set-cookie: _nss=1; path=/; secure; HttpOnly; SameSite=Strict
x-content-type-options: nosniff
x-xss-protection: 1;mode=block
content-type: application/json

"This is example of static text"

@f3l1x Thank you, man! It's finally working for me. I got it almost right before, now that I look at it again... I'm basically dumb, it's that simple. 🤦‍♂️

Anyway, thanks again for providing the correct example, we probably couldn’t have pulled this off without you! Really appreciate that. May you be well! :)

May the force be with you too. ;-)