In the first part, we used Socialite to register and log in the user through the Slack OAuth provider. This article will dive deeper into the inner workings of Socialite, exploring how the package operates behind the scenes to allow logging in with different authentication providers.

Pre-requisites:

  • A general understanding of Socialite login (I recommend following the first part).
  • A basic understanding of the Facade pattern would be helpful, but don’t worry; I will explain it.

In the last article, we saw that the Slack login process is initiated as follows:

PHP
use Laravel\Socialite\Facades\Socialite;

Socialite::driver('slack')->redirect();

Part 1: How Socialite::driver('slack') is Resolved

Let us examine the Socialite driver class. We can find the following definition of the facade accessor:

PHP
protected static function getFacadeAccessor()
{
    return Factory::class;
}

Generally speaking, every facade class has the getFacadeAccessor method, which provides an abstract implementation. A concrete implementation is typically provided in the service providers. For example, if we navigate to SocialiteServiceProvider, we can see the following:

PHP
$this->app->singleton(Factory::class, function ($app) {
    return new SocialiteManager($app);
});

The Factory facade provides a singleton instance of the SocialiteManager class. The facade pattern is widely used across the Laravel framework.

Let’s explore the SocialiteManager class to understand how the Slack driver is initialized. The SocialiteManager class does not have the driver method, but it defines the drivers for several OAuth service providers such as GitHub, Facebook, Google, and Slack. The base class Manager includes the following driver method:

PHP
public function driver($driver = null)
{
    $driver = $driver ?: $this->getDefaultDriver();

    if (is_null($driver)) {
        throw new InvalidArgumentException(sprintf(
            'Unable to resolve NULL driver for [%s].', static::class
        ));
    }

    if (!isset($this->drivers[$driver])) {
        $this->drivers[$driver] = $this->createDriver($driver);
    }

    return $this->drivers[$driver];
}

Here, we check if a driver has been defined previously; if so, the previously defined driver is returned. Otherwise, the createDriver function is called. This is a Singleton pattern, ensuring that only one instance of a class exists throughout the request lifecycle.

PHP
protected function createDriver($driver)
{
    if (isset($this->customCreators[$driver])) {
        return $this->callCustomCreator($driver);
    }

    $method = 'create' . Str::studly($driver) . 'Driver';

    if (method_exists($this, $method)) {
        return $this->$method();
    }

    throw new InvalidArgumentException("Driver [$driver] is not supported.");
}

The important part is $method = 'create' . Str::studly($driver) . 'Driver'; where we are creating the desired driver. For example:

PHP
protected function createSlackDriver()
{
    $config = $this->config->get('services.slack');

    return $this->buildProvider(
        SlackProvider::class, $config
    );
}

The SlackProvider class is responsible for the entire Slack OAuth process. If we look at the buildProvider function, we see that it returns an instance of the OAuth service along with user-provided credentials:

PHP
public function buildProvider($provider, $config)
{
    return new $provider(
        $this->container->make('request'),
        $config['client_id'],
        $config['client_secret'],
        $this->formatRedirectUrl($config),
        Arr::get($config, 'guzzle', [])
    );
}

Replacing $providerwith SlackProvider we end up with the following

PHP
new SlackProvider(
    $this->container->make('request'),
    $config['client_id'],
    $config['client_secret'],
    $this->formatRedirectUrl($config),
    Arr::get($config, 'guzzle', [])
);

Thus, the Socialite::driver('slack') is resolved!

Part 2: How the redirect works

Let us figure out the redirect part in Socialite::driver('slack')->redirect();

As from the slack documentation the slack authentication is triggered by visiting the following route https://slack.com/oauth/v2/authorize. So we need to figure out how the slack socialite driver calls this. The SlackProvider class extends the AbstractProvider class that defines the constructor and a redirect function

PHP
public function redirect()
    {
        $state = null;

        if ($this->usesState()) {
            $this->request->session()->put('state', $state = $this->getState());
        }

        if ($this->usesPKCE()) {
            $this->request->session()->put('code_verifier', $this->getCodeVerifier());
        }

        return new RedirectResponse($this->getAuthUrl($state));
    }

We are most interested in the usesState condition because PKCE is used for stateless authentication for less secure devices which involves exchanging the tokens. We can further see that it defines the getAuthUrl method. Here we can find the slack authorization link which triggers the slack login process

PHP
public function getAuthUrl($state)
    {
        return $this->buildAuthUrlFromBase('https://slack.com/oauth/v2/authorize', $state);
    }

We are getting a few steps closer. Next, we can see that the buildAuthUrlFromBase is creating the url query string

PHP
protected function buildAuthUrlFromBase($url, $state)
    {
        return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
    }

The getCodeFields function returns all the necessary parameters required for the above url. The include the client_id, redirect_uri, scope (permissions), response_type and the state.

PHP
protected function getCodeFields($state = null)
    {
        $fields = [
            'client_id' => $this->clientId,
            'redirect_uri' => $this->redirectUrl,
            'scope' => $this->formatScopes($this->getScopes(), $this->scopeSeparator),
            'response_type' => 'code',
        ];

        if ($this->usesState()) {
            $fields['state'] = $state;
        }

        if ($this->usesPKCE()) {
            $fields['code_challenge'] = $this->getCodeChallenge();
            $fields['code_challenge_method'] = $this->getCodeChallengeMethod();
        }

        return array_merge($fields, $this->parameters);
    }

When the above code is executed the slack login process is initiated and the slack will prompt the user to allow accessing their data. After the user accepts, they will be redirected to the redirect url specified in the config with the necessary information of the user.

PHP
$user = Socialite::driver('slack')->user();

We can then perform any business logic on this information received, i.e., registering the user or logging them in. Well, that is how the oauth works with slack provider. Pretty cool, right!?

Conclusion

In this article, we learned how the socialite works behind the scenes for the slack driver. Socialite is an amazing tool that allows us to implement different oauth service providers within our application. While there are officially supported drivers for the socialite, we also have an option to create our own drivers. It is also possible to overwrite any existing drivers. Feel free to dive into them to learn more about the amazing package that is the socialite! 😎 πŸ™Œ