OAuth Authorization URL for Shopify app development

Topic summary

A developer is building a Shopify public app (sales channel) using Laravel and Polaris, hosted on Heroku, but encountering OAuth authorization issues.

Core Problem:

  • When clicking “Install app,” the application redirects to the standalone Heroku-hosted Laravel app instead of embedding within the Shopify admin dashboard
  • The OAuth callback redirection authentication isn’t working as expected

Technical Setup:

  • App configured with app URL and redirect URL (/auth/shopify) in Shopify Partner dashboard
  • Laravel routes established for OAuth flow (/auth/shopify and /auth/shopify/callback)
  • Controller implements OAuth authorization with scopes (read_products, write_products)

Attempted Solutions:

  • Added Content-Security-Policy header with frame-ancestors directive
  • Implemented state cookie for CSRF protection
  • Checked for embedded header to determine redirect method
  • Code includes access token exchange logic

Current Status:
The discussion remains ongoing with the developer seeking additional guidance. YOD_Solutions has provided implementation advice referencing Shopify’s authorization code grant documentation, emphasizing proper CSP header configuration and state token handling. The developer has updated their code accordingly but the embedding issue persists, requesting further code review.

Summarized with AI on November 10. AI used: claude-sonnet-4-5-20250929.

Hi there!

I am building a Shopify public app (sales channel) using Shopify Polaris and Laravel, hosted on Heroku. I created the app on the Shopify Partner account dashboard and configured it with an app URL and a redirection URL. For the app URL, I added the public link for the Laravel application provided by Heroku. And for the redirection URL, I added the same link followed by /auth/shopify.

After that I set laravel routes as follows.

Route::get('/auth/shopify', [ShopifyAuthController::class, 'redirectToShopify']);
Route::get('/auth/shopify/callback', [ShopifyAuthController::class, 'handleShopifyCallback']);

And here are functions in ShopifyAuthController.

  public function redirectToShopify()
    {
        $shopifyDomain = request('shop');
        $scopes = ['read_products', 'write_products']; // Add necessary scopes for your app
    
        $query = http_build_query([
            'client_id' => env('SHOPIFY_API_KEY'),
            'redirect_uri' => env('SHOPIFY_REDIRECT_URI'),
            'scope' => implode(',', $scopes),
            'state' => csrf_token(),
        ]);
    
        // Construct the OAuth authorization URL
        $authorizationUrl = "https://{$shopifyDomain}/admin/oauth/authorize?{$query}";
    
        return redirect($authorizationUrl);
    }
    
    public function handleShopifyCallback()
    {
        $code = request('code');
        $state = request('state');

        if ($state === null || !hash_equals(csrf_token(), $state)) {
            abort(403, 'Invalid state parameter.');
        }

        // Extract store domain from the callback URL
        $storeDomain = request('shop'); // 'shop' is the query parameter containing the store domain

        // Construct the OAuth endpoint URL for the specific store
        $oauthEndpointUrl = "https://{$storeDomain}/admin/oauth/access_token";

        try {
            $response = Http::post($oauthEndpointUrl, [
                'client_id' => env('SHOPIFY_API_KEY'),
                'client_secret' => env('SHOPIFY_API_SECRET'),
                'code' => $code,
            ]);

            $responseBody = $response->json();

            if (isset($responseBody['access_token'])) {
                return redirect('/home');
            } else {
                throw new \Exception('Access token not found in response.');
            }
        } catch (\Exception $e) {
            // Log or handle the error appropriately
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }

Now when I click on the ‘Install app’ button to add the app to my dev store, it redirects to the application hosted on Heroku instead of being embedded in the Shopify dashboard. Additionally, in the inspect console, I can see the following information.

hmac: xxxxx 
host: xxxxx
shop: sales-channel-test.myshopify.com
timestamp: 1713581168

I’m not sure what is missing from my configuration. I think the authentication redirection callback isn’t working.

Could you help me resolve this?

Thank you!

Your redirect header needs to contain:

“Content-Security-Policy”: “frame-ancestors https://”+shop+" https://admin.shopify.com" for your app to be embedded in Shopify admin iframe.

You also need to check if the “embedded” header is set to 1 or 0 - to decide whether you need to redirect from server side or frontend using App Bridge. Your redirection needs to include the state cookie containing a signed state token. Everything is documented here: Implement authorization code grant manually (shopify.dev)

Hi, YOD_Solutions!
Thank you for your kind response!
I have updated the functions according to your reply, but it still shows same.
It opens the original Laravel app hosted on Heroku, not embedded in Shopify dashboard.

Here is updated ShopifyAuthController.

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cookie;

class ShopifyAuthController extends Controller
{
    public function redirectToShopify(Request $request)
    {
        $shopifyDomain = $request->input('shop');
        $embedded = $request->header('embedded');
    
        if ($embedded === '1') {
            // If embedded header is set to 1, redirect from frontend using App Bridge
            // Return a response indicating that the redirection will be handled in frontend
            return response()->json(['message' => 'Redirecting from frontend using App Bridge']);
        } else {
            // If embedded header is not set or set to 0, redirect from server-side
            // Construct the OAuth authorization URL
            $scopes = ['read_products', 'write_products']; // Add necessary scopes for your app
            $query = http_build_query([
                'client_id' => env('SHOPIFY_API_KEY'),
                'redirect_uri' => env('SHOPIFY_REDIRECT_URI'),
                'scope' => implode(',', $scopes),
                'state' => csrf_token(),
            ]);
            $authorizationUrl = "https://{$shopifyDomain}/admin/oauth/authorize?{$query}";
    
            // Set the Content-Security-Policy header
            $response = response()->make(redirect($authorizationUrl));
            $response->header('Content-Security-Policy', 'frame-ancestors https://' . $shopifyDomain . ' https://admin.shopify.com');
    
            // Generate and set state cookie
            $state = csrf_token();
            $response->withCookie(Cookie::make('state', $state));
    
            return $response;
        }
    }   

    public function handleShopifyCallback(Request $request)
    {
        // Retrieve the state token from the request
        $state = $request->input('state');
    
        // Verify the state token to ensure it matches the expected value
        if ($state === null || !hash_equals($state, $request->session()->pull('state'))) {
            // If the state token doesn't match, abort the process with an error
            abort(403, 'Invalid state parameter.');
        }
    
        // Retrieve other parameters from the request, such as code and shop
        $code = $request->input('code');
        $storeDomain = $request->input('shop');
    
        // Construct the OAuth endpoint URL for the specific store
        $oauthEndpointUrl = "https://{$storeDomain}/admin/oauth/access_token";
    
        try {
            $response = Http::post($oauthEndpointUrl, [
                'client_id' => env('SHOPIFY_API_KEY'),
                'client_secret' => env('SHOPIFY_API_SECRET'),
                'code' => $code,
            ]);
    
            $responseBody = $response->json();
    
            if (isset($responseBody['access_token'])) {
                // You'll need this access token for making API requests to Shopify
                return redirect('/home');
            } else {
                throw new \Exception('Access token not found in response.');
            }
        } catch (\Exception $e) {
            // Log or handle the error appropriately
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }    
}

PREVIEW

PREVIEW

The content security policy header should be “frame-ancestors https://your-shop.myshopify.com https://admin.shopify.com” - replace “your-shop” with your shop myshopify domain name. For additional info, please refer to the shopify document I included earlier. Everything is elaborated there.

Hi YOD_Solutions! I already set the content security policy header as you can see my last code. Would you mind checking it and give me an advice? Thank you so much!