Search code examples
laravellaravel-5laravel-socialite

Laravel 5 Socialite - change auth redirect path dynamically


I am trying to incorporate Socialite into a site and have come across a problem with what should be a rather trivial thing to do - change where the user is redirected after authenticated by a social provider.

I have three actions that I will be using Socialite for:

  • Register - Authenticate with the provider and then redirect to the Registration Form. Their name and email address are filled in for them, more fields are required.
  • Login - Typical login scenario, the provider information is checked against the Users table and they are logged in if there is a match
  • Link - User already has an active account but would like to link an external (Faceebook or Google) account. This authenticates the user and adds the proper social media ID to their row in the Users table. The problem is that you specify the authentication redirect in the services.php file, I cannot figure out how to change where the user is redirected after successfully authenticating with the third party provider.

Here is what I have so far -

Route: This seems to work up to when the social provider redirects, because driver=facebook&action=login (for example) is missing. Ideally, I'd like to specify where the user is redirected when the initial auth request is made - this was just my first attempt at figuring this out.

get('social-auth', function(AuthenticateUser $authenticateUser, Request $request) {
    $driver = $request->input('driver');
    $action = $request->input('action');

    return $authenticateUser->execute($request->has('code'), $driver, $action);
});

AuthenticateUser class - Hopefully the comments here are sufficient:

<?php namespace App;

use Illuminate\Database\Eloquent\ModelNotFoundException;
use Laravel\Socialite\Contracts\Factory as Socialite;
use Auth;
use Flash;

class AuthenticateUser
{
    /**
     * @var Socialite
     */
    private $socialite;
    /**
     * @var Auth
     */
    private $auth;

    public function __construct(Socialite $socialite, Auth $auth)
    {

        $this->socialite = $socialite;
        $this->auth = $auth;
    }

    /**
     * @param boolean $hasCode Whether or not we have been authenticated already
     * @param string $driver Driver to use, Facebook or Google
     * @param string $type Type of call - Login, Register, or Link
     * @return array|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function execute($hasCode, $driver, $type)
    {
        if (!$hasCode) {
            return $this->getAuthorizationFirst($driver);
        }

        // Get the User details from the Social Provider
        $socialUser = $this->socialite->driver($driver)->user();
        $socialUserArray = $socialUser->user;

        if ($driver == 'facebook') {
            // Get Facebook specific fields
            $first_name = $socialUserArray['first_name'];
            $last_name = $socialUserArray['last_name'];
        } else if ($driver == 'google') {
            // Get Google specific fields
            $first_name = $socialUserArray['name']['givenName'];
            $last_name = $socialUserArray['name']['familyName'];
        }
        $email = $socialUser->email;
        $id = $socialUser->id;

        // Perform an action - login, register, or link
        switch ($type) {
            case 'login':
                // Log the user in with the Facebook ID
                try {
                    if ($driver == 'facebook') {
                        $user = User::whereFacebookAuth($id)->firstOrFail();
                    } else {
                        $user = User::whereGoogleAuth($id);
                    }
                } catch (ModelNotFoundException $e) {
                    flash::error('Could not find a user associated with this ' . ucfirst($driver) . ' account.');
                    return redirect('auth/login');
                }
                Auth::login($user);
                return redirect('members');
                break;
            case 'register':
                // Register using social media account
                return redirect('register')
                    ->with('social_type', $driver)
                    ->with('social_id', $id)
                    ->with('email', $email)
                    ->with('first_name', $first_name)
                    ->with('last_name', $last_name);
                break;
            case 'link':
                // Associate this Social Media account with the current User
                $driver == 'facebook' ? Auth::user()->facebook_auth = $id : Auth::user()->google_auth = $id;
                Auth::user()->save();
                return ['status' => 'success'];
                break;
        }
    }

    /**
     * Authorize the user before proceeding
     * @param $driver
     * @return mixed
     */
    private function getAuthorizationFirst($driver)
    {
        return $this->socialite->with($driver)->redirect('hopefully-somewhere-else'); 
    // Anything inside redirect doesn't seem to do anything
    }
}

Solution

  • I tend to include the provider in my URLs. For example:

    Route::get('auth/{provider}', 'AuthController@redirectToProvider');
    Route::get('auth/{provider}/callback', 'AuthController@handleProviderCallback');
    

    That way I can access the provider name from the request in my controller actions:

    public function callback($provider)
    {
        $user = $this->socialite->driver($provider)->getUser();
    
        try {
            // Try and find user by their social profile UID
            $appUser = SocialAccount::whereUid($user->getId())
                ->whereProvider($provider)
                ->firstOrFail();
    
            Auth::loginUsingId($appUser->user_id);
        } catch (ModelNotFoundException $e) {
            if (Auth::user()) {
                // Attach social profile to logged in user
            } else {
                // User is not logged in, and account does not exist
                // Prompt to register
            }
        }
    }