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:
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:
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:
$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:
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.
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:
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:
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
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
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
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
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.
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.
$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! π π