thebigk's avatar
Level 13

Laravel Socialite for Multi-Tenant App: Set Dynamic Config Settings

I am building a multi-tenant application and want to provide social login to the tenants. I noticed that Laravel socialite requires me to set the following in the config/services.php file -

'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect' => 'http://example.com/callback-url',
    ],

In my case, I need to pull the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET via database; as these values will be provided by each tenant for their domain.

How should I approach this problem?

0 likes
9 replies
LaryAI's avatar
Level 58

To handle dynamic configuration settings for Laravel Socialite in a multi-tenant application, you can override the configuration at runtime before you make the call to Socialite. You can achieve this by setting the configuration using the Config facade.

Here's a step-by-step solution:

  1. Create a middleware or a method in your controller that will be executed before the Socialite call. This middleware/method will fetch the tenant-specific settings from the database and set the configuration.

  2. Use the Config facade to set the configuration values dynamically.

Here's an example of how you might implement this:

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

class SocialiteConfigurator
{
    public function handle(Request $request, $next)
    {
        // Assume you have a method to get the tenant-specific settings
        $tenantSettings = $this->getTenantSettings($request);

        // Set the configuration for Socialite dynamically
        Config::set('services.google.client_id', $tenantSettings->google_client_id);
        Config::set('services.google.client_secret', $tenantSettings->google_client_secret);
        Config::set('services.google.redirect', $tenantSettings->google_redirect);

        return $next($request);
    }

    protected function getTenantSettings(Request $request)
    {
        // Logic to retrieve tenant settings based on the domain or any other identifier
        // For example, you might get the tenant ID from the subdomain or a session variable
        $tenantId = $this->getTenantIdFromRequest($request);

        // Fetch the settings from the database
        $tenantSettings = Tenant::find($tenantId)->settings;

        return $tenantSettings;
    }

    protected function getTenantIdFromRequest(Request $request)
    {
        // Implement logic to extract tenant ID from the request
        // This is just a placeholder for the actual implementation
        return $request->get('tenant_id');
    }
}
  1. Register the middleware in your app/Http/Kernel.php if you're using middleware, or simply call the method before initiating the Socialite call in your controller.

  2. When you redirect the user to the OAuth provider, the configuration will be set to the tenant-specific values.

Remember to ensure that the middleware or method is called before every Socialite operation that requires the tenant-specific settings. This way, the configuration will always be up-to-date with the correct tenant's information.

Please note that the above code is a simplified example and assumes you have a Tenant model with a settings relationship or attribute that contains the OAuth credentials. You will need to adjust the getTenantSettings and getTenantIdFromRequest methods to match your application's logic for identifying tenants and retrieving their settings.

1 like
martinbean's avatar
Level 80

@thebigk You can override or set configuration values at runtime. So when you’ve resolved your tenant (however you do that) you can then set the configuration values in the application:

config([
    'services' => [
        'google' => [
            'client_id' => $tenant->google_client_id,
            'client_secret' => $tenant->google_client_secret,
            'redirect' => $tenant->google_redirect,
        ],
    ].
])

From https://laravel.com/docs/10.x/configuration#accessing-configuration-values:

To set configuration values at runtime, pass an array to the config function

1 like
thebigk's avatar
Level 13

@martinbean This is interesting. I should have read the docs. Does this mean I can do this within the controller?

config([
    'services' => [
        'google' => [
            'client_id' => $tenant->google_client_id,
            'client_secret' => $tenant->google_client_secret,
            'redirect' => $tenant->google_redirect,
        ],
    ].
])

return Socialite::driver('google')
    ->setScopes(['read:user', 'public_repo'])
    ->redirect();

Because I need to dynamically configure the driver to work for each tenant.

martinbean's avatar

@thebigk I’d do it in middleware after you‘ve actually resolved the tenant. If you do it in a controller, then you’re going to have to do it in every controller action where you need those configuration values.

1 like
thebigk's avatar
Level 13

@martinbean makes sense. I'll do it in controller. I was wondering if the driver basically simply accepted an array comprising ['client_id', 'client_secret', 'redirect'].

martinbean's avatar

makes sense. I'll do it in controller

🤦‍♂️

thebigk's avatar
Level 13

@martinbean My bad. I wanted to type 'middleware'; but looks like I was picturing adding that code in the controller. (⊙ _ ⊙ )

jigsaw5279's avatar

Doing it in either controller or middleware felt wrong, so I ended up registering it as a custom service in the container:

$this->app->bind('socialite.microsoft', function (Application $app) {
            $settings = $app->get(MicrosoftSettings::class);

            Config::set('services.microsoft', [
                'client_id' => $settings->client_id,
                'client_secret' => $settings->client_secret,
                'redirect' => $settings->redirect,
                'tenant' => $settings->tenant,
            ]);

            return Socialite::driver('microsoft');
        });

Note: I'm using Spatie's settings package

Please or to participate in this conversation.