Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue SPA and Laravel Socialite

I am trying to integrate social logins via laravel api backend using laravel socialite stateless option since I am building a single page app with vuejs and everything is done via http api calls.

However I am facing an issue with the social callback, current callback is being sent to laravel backend and it works, but now I want to return the user to the same page before starting the authentication, any idea on how to come around this ?

public function handleProviderCallback($provider)
{
    $user = Socialite::driver($provider)->stateless()->user();

    // dont know how to return the user to the last page on vue
} 
like image 772
Aladin Small Avatar asked Jan 02 '23 12:01

Aladin Small


1 Answers

I was working on something similar and I found a solution. The code is based in this great starter theme: https://github.com/cretueusebiu/laravel-vue-spa

--

In the handleProviderCallback(), assuming that you are using Passport API Authentication, you can try with this for the Controller:

public function handleProviderCallback($provider)
    {
        $user = Socialite::driver($provider)->stateless()->user();

        /* HERE CREATE USER WITH YOUR APP LOGIC. If email is unique... */

        // Login the created user
        Auth::login($user, true);

        // Get the username (or wathever you want to return in the JWT).
        $success['name'] = Auth::user()->name;
        // Create a new access_token for the session (Passport)
        $success['token'] = Auth::user()->createToken('MyApp')->accessToken;

        // Create new view (I use callback.blade.php), and send the token and the name.
        return view('callback', [
            'name' => $success['name'],
            'token' => $success['token'],
        ]);
    }

For the callback.blade.php view, the only thing you need is to send the requested token and username to the Vue app. For this you can use window.postMessage() method that allows to send data between windows, iframes...

<html>
<head>
  <meta charset="utf-8">
  <title>Callback</title>
  <script>
    window.opener.postMessage({ token: "{{ $token }}", name: "{{ $name }}" }, "YOUR DOMAIN");
    window.close();
  </script>
</head>
<body>
</body>
</html>

And finally this is my logic to the Login component in the vue app:

export default {
        // Waiting for the callback.blade.php message... (token and username).
        mounted () {
          window.addEventListener('message', this.onMessage, false)
        },

        beforeDestroy () {
          window.removeEventListener('message', this.onMessage)
        },

        methods : {
            // This method call the function to launch the popup and makes the request to the controller. 
            loginGoogle () {
              const newWindow = openWindow('', 'message')
              axios.post('api/login-google')
                    .then(response => {
                      newWindow.location.href = response.data;
                    })
                    .catch(function (error) {
                      console.error(error);
                    });
              },
              // This method save the new token and username
              onMessage (e) {
                if (e.origin !== window.origin || !e.data.token) {
                  return
                }
                localStorage.setItem('user',e.data.name)
                localStorage.setItem('jwt',e.data.token)

                this.$router.go('/board')
              }
        }
    }

    // The popup is launched.

    function openWindow (url, title, options = {}) {
      if (typeof url === 'object') {
        options = url
        url = ''
      }

      options = { url, title, width: 600, height: 720, ...options }

      const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screen.left
      const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screen.top
      const width = window.innerWidth || document.documentElement.clientWidth || window.screen.width
      const height = window.innerHeight || document.documentElement.clientHeight || window.screen.height

      options.left = ((width / 2) - (options.width / 2)) + dualScreenLeft
      options.top = ((height / 2) - (options.height / 2)) + dualScreenTop

      const optionsStr = Object.keys(options).reduce((acc, key) => {
        acc.push(`${key}=${options[key]}`)
        return acc
      }, []).join(',')

      const newWindow = window.open(url, title, optionsStr)

      if (window.focus) {
        newWindow.focus()
      }

      return newWindow
    }

</script> 

I hope it helps you!

like image 200
asiermusa Avatar answered Jan 05 '23 07:01

asiermusa