laravel-shopify icon indicating copy to clipboard operation
laravel-shopify copied to clipboard

App must set security headers to protect against clickjacking

Open devRonak opened this issue 3 years ago • 26 comments

Hello @osiset, My App was rejected by Shopify due to the bellow reason. please help.

App must set security headers to protect against clickjacking. Your app does not request installation on the shop immediately after clicking "add app". Apps must ask a shop for access when being installed on a shop for the first time, as well as when they are being reinstalled after having been removed. During install or reinstall we expected OAuth to be initiated at https://cambridgetestshop.myshopify.com/admin/oauth/request_grant but was redirected to https://cambridgetestshop.myshopify.com/admin/apps/0dc7ae777517adc333ebd1aeb6f5a556/authenticate/token?shop=cambridgetestshop.myshopify.com&target=%2F. Learn more about authentication in our developer documentation

I installed the package on fresh laravel and tried to install it on Shopify, but the URL changes. Please see below screenshot

screenshot network

Laravel version - 8.75.0 Package version - 17.1.0 php version - 8.0.11

devRonak avatar Feb 01 '22 09:02 devRonak

Same issue. We got rejected because of the same reason. Any workaround to solve this?

sarmadmakhdoom avatar Feb 01 '22 11:02 sarmadmakhdoom

Try phpclassic/php-shopify which is easier and better. You can have more control over the installation process.

afnan125 avatar Feb 04 '22 06:02 afnan125

Hi.

First off, let me tell you something. I use a translation app because I'm not very good at English. sorry.

The following header must be set for security purposes.

https://github.com/osiset/laravel-shopify/wiki/Installation#appbridge

You will also need to set the following header to allow iframes.

https://shopify.dev/apps/store/security/iframe-protection

The solution to the Content-Security-Policy header was discussed in this issue.

Migrate app from Shopify App Bridge 1.x to Shopify App Bridge 2.0

However, I used a different method to create the process and resubmit the application.

inaryu avatar Feb 10 '22 10:02 inaryu

Hi, my app also got rejected because of the same reason. Please help.

pangiras avatar Feb 10 '22 17:02 pangiras

I don't know this is a permanent solution or library will provide a better one but we have added a middleware for now to handle this problem and our app pass the initial automatic verification step after resubmission.

class FrameHeadersMiddleware
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);
        $user = \Auth::user();
        if($user){
            $response->header('Content-Security-Policy', "frame-ancestors https://{$user->name} https://admin.shopify.com");
        }
        return $response;
    }
}

sarmadmakhdoom avatar Feb 10 '22 17:02 sarmadmakhdoom

[sarmadmakhdoom] is your app working with this solution ?

udaantech avatar Feb 11 '22 05:02 udaantech

Thanks @sarmadmakhdoom this really worked

afnan125 avatar Feb 11 '22 05:02 afnan125

[sarmadmakhdoom] is your app working with this solution ?

Yeah it’s working fine after this. Do you see any issues after adding this middleware?

sarmadmakhdoom avatar Feb 11 '22 07:02 sarmadmakhdoom

Hi @sarmadmakhdoom. Are you using this middleware globally or as a group middleware. I used this in group middleware, still my app got rejected for the same reason. Though I can see that the content-security-policy has been set in response headers for all the routes. What can be possibly wrong? Please help.

pangiras avatar Feb 11 '22 09:02 pangiras

Hi @sarmadmakhdoom. Are you using this middleware globally or as a group middleware. I used this in group middleware, still my app got rejected for the same reason. Though I can see that the content-security-policy has been set in response headers for all the routes. What can be possibly wrong? Please help.

I am using it as global middleware, try that please

sarmadmakhdoom avatar Feb 11 '22 09:02 sarmadmakhdoom

Hi @sarmadmakhdoom below is my middleware

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class FrameHeadersMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);
        $user = \Auth::user();
        if($user){
            $response->header('Content-Security-Policy', "frame-ancestors https://{$user->name} https://admin.shopify.com");
        }
        return $response;
    }
}

Add below is my kernel.php, you can see that I have registered it as global middleware

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array<int, class-string|string>
     */
    protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\FrameHeadersMiddleware::class,
    ];

I can see by inspecting the page that content-security-policy is being set on all the http requests. Still my app is getting auto rejected. I am lost here and have no idea about what to do.

Please help.

pangiras avatar Feb 12 '22 10:02 pangiras

Hi @pangiras

Do you have X-Frame-Options set in your header?

inaryu avatar Feb 12 '22 13:02 inaryu

Hi @inaryu

No I haven't set X-Frame_Options, and my application runs in App Bridge.

pangiras avatar Feb 12 '22 15:02 pangiras

Hi @pangiras

Currently the laravel-shopify library has a bug when installing the app. https://github.com/osiset/laravel-shopify/issues/1071 This may be the reason why your application is rejected.

inaryu avatar Feb 13 '22 04:02 inaryu

Hi @pangiras

Currently the laravel-shopify library has a bug when installing the app. #1071 This may be the reason why your application is rejected.

Hi @inaryu

I can confirm that this is not the problem, as I have implemented the solution mentioned by you in #1071 and that has fixed the bug.

pangiras avatar Feb 13 '22 06:02 pangiras

@pangiras If so, there may be another cause. I also submitted my app after making this fix, and so far it has not been rejected. Do you have any special app settings?

inaryu avatar Feb 13 '22 07:02 inaryu

Hi @inaryu and @sarmadmakhdoom. Thank you for the support provided, I really appreciate your effort and time.

I was able to get past the auto rejection using two fixes.

  1. I added the middleware as mentioned by @sarmadmakhdoom.
  2. I added the following header to this directory in my case /vendor/osiset/laravel-shopify/src/resources/views/layouts/default.blade.php , as I am extending default.blade.php in all the views.
<?php
  $domain=$shopDomain ?? Auth::user()->name ;
  header("Content-Security-Policy: frame-ancestors https://".$domain." "."https://admin.shopify.com");
?>

pangiras avatar Feb 15 '22 07:02 pangiras

Hi @pangiras Glad we could work it out! By the way, I submitted an application with this response.

  1. Create middleware.
  2. Add middleware to the web middleware group
  • Processing in middleware (app/Http/Middleware/AddContentSecurityPolicyHeader.php)
<?php

namespace App\Http\Middleware;

use App\Models\User;
use Closure;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Osiset\ShopifyApp\Objects\Values\ShopDomain;

class AddContentSecurityPolicyHeader
{
    protected const ADMIN_SHOPIFY_URL = 'https://admin.shopify.com';

    protected const HEADER_FORMAT = 'frame-ancestors %s %s';

    public function handle(Request $request, Closure $next)
    {
        /** @var Response|RedirectResponse $response */
        $response = $next($request);

        // If it's not a redirect response and it's not Ajax
        if ($response instanceof Response && !$request->ajax()) {
            // If the request contains shop data, get the shop data
            if ($request->has('shop')) {
                $shopDomain = ShopDomain::fromNative($request->get('shop'));
            } elseif ($request->user() instanceof User) {
                // Get from user data
                $shopDomain = $request->user()->getDomain();
            } else {
                // If you still can't get it, get it from the request data.
                $shopDomain = ShopDomain::fromRequest($request);
            }

            if ($shopDomain instanceof ShopDomain) {
                $response->header('Content-Security-Policy', sprintf(self::HEADER_FORMAT, 'https://' . $shopDomain->toNative(), self::ADMIN_SHOPIFY_URL));
            }
        }

        return $response;
    }
}
  • Add middleware to the web middleware group in app/Http/Kernel.php
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            // add middleware
            \App\Http\Middleware\AddContentSecurityPolicyHeader::class,

        ],

        'api' => [
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

I think adding it to the web middleware group will handle most of the requests. This also means that you probably don't need to add it to your layout file.

inaryu avatar Feb 15 '22 07:02 inaryu

Hi @inaryu ,

I also first started out by creating the group middleware and did add into web middleware group and I could see that all the requests made had content-security-policy set, still the app got auto rejected. And then I changed it to global middleware to eliminate any doubts, still the app got auto rejected. And then I tried the fix that solved my problem. Though it isn't the best practice as I can see that in some requests the csp is repeated, but that won't do any harm I suppose. I like the way you have implemented the middleware, may it also help others facing the same problem.

pangiras avatar Feb 15 '22 08:02 pangiras

Hi @inaryu and @sarmadmakhdoom. Thank you for the support provided, I really appreciate your effort and time.

I was able to get past the auto rejection using two fixes.

  1. I added the middleware as mentioned by @sarmadmakhdoom.
  2. I added the following header to this directory in my case /vendor/osiset/laravel-shopify/src/resources/views/layouts/default.blade.php , as I am extending default.blade.php in all the views.
<?php
  $domain=$shopDomain ?? Auth::user()->name ;
  header("Content-Security-Policy: frame-ancestors https://".$domain." "."https://admin.shopify.com");
?>

You’re welcome. I’m glad I was able to help

sarmadmakhdoom avatar Feb 15 '22 09:02 sarmadmakhdoom

@inaryu @sarmadmakhdoom - Feel free to submit a PR to help fix this issue for the wider package.

Kyon147 avatar Feb 16 '22 16:02 Kyon147

I followed the as mentioned by @sarmadmakhdoom. But still app gets rejected.

Anyone know what is the issue ?

ingalesachin7 avatar Apr 27 '22 09:04 ingalesachin7

Rejection can be based on a number of things - Shopify will tell you why in their response to you. So you would need to look at that and see what you need to do.

Kyon147 avatar Apr 27 '22 09:04 Kyon147

The everytime getting same feedback-

App must set security headers to protect against clickjacking. Your app must set the proper frame-ancestors content security policy directive to avoid clickjacking attacks. The 'content-security-policy' header should set frame-ancestors https://[shop].myshopify.com/ https://admin.shopify.com/, where [shop] is the shop domain the app is embedded on.

I can see by inspecting the page that content-security-policy is being set on all the http requests

ingalesachin7 avatar Apr 27 '22 09:04 ingalesachin7

@ingalesachin7 did you find any solution? I am getting the same response in every rejection.

manpreet981 avatar May 31 '22 12:05 manpreet981

https://github.com/osiset/laravel-shopify/issues/1070#issuecomment-1039959161

I used this solution mentioned by @inaryu And

<?php
  $domain=$shopDomain ?? Auth::user()->name ;
  header("Content-Security-Policy: frame-ancestors https://".$domain." "."https://admin.shopify.com");
?> 

added this on layout page.

Solution is accpeted.

ingalesachin7 avatar May 31 '22 12:05 ingalesachin7

Released in v17.2.0

Kyon147 avatar Sep 11 '22 07:09 Kyon147