Laravel Socialite token refreshing
Asked Answered
D

2

7

The access_token acquired by Socialite (via Socialite::driver(self::PROVIDER)->user() has a limited time validity. For Google it's an hour.

I'm able to get refresh_token by changing the redirect call to:

Socialite::driver(self::PROVIDER)->stateless()->with([
    'access_type' => 'offline',
])->redirect()

For an hour I'm able to read user data based on access_token by calling

// $token = read_stored_access_token()
\Socialite::driver(self::PROVIDER)->userFromToken($accessToken);

After an hour, when the token gets invalid, the Google API starts returning 401 Unauthorized and Socialite propagates this out:

(1/1) ClientException
Client error: `GET https://www.googleapis.com/plus/v1/people/me?prettyPrint=false` resulted in a `401 Unauthorized` response:
{"error":{"errors":[{"domain":"global","reason":"authError","message":"Invalid Credentials","locationType":"header","loc (truncated...)

Now with the refresh_token, I should be able to easily refresh the access_token. But I cannot find a mention in Socialite docs or source code which would allow me to do that.

Is really the only way how to accomplish this use Google's API library and do this manually? Doesn't it kill the whole idea of using Socialite?

Note: I'm trying to avoid to call redirect() again as it might force user to pick one of his Google accounts every hour which is annoying.

Thanks!

Defector answered 11/7, 2017 at 13:12 Comment(1)
And a followup question: What's the point of providing access to refresh_token if one cannot use it further without implementing his own client manually?Defector
B
5

Here is the way I saving user from Socialite with offline access:

            $newUser                       = new User;
            $newUser->name                 = $user->name;
            $newUser->email                = $user->email;
            $newUser->google_id            = $user->id;
            $newUser->google_token         = $user->token;
            $newUser->token_expires_at     = Carbon::now()->addSeconds($user->expiresIn);
            $newUser->google_refresh_token = $user->refreshToken;
            $newUser->avatar               = $user->avatar;
            $newUser->avatar_original      = $user->avatar_original;
            $newUser->save();

And here is my solution for token refreshing. I made it via creating accessor for token attribute in my User model:

    /**
     * Accessor for google token of the user
     * Need for token refreshing when it has expired
     *
     * @param $token
     *
     * @return string
     */
    public function getGoogleTokenAttribute( $token ) {
        //Checking if the token has expired
        if (Carbon::now()->gt(Carbon::parse($this->token_expires_at))) {
            $url  = "https://www.googleapis.com/oauth2/v4/token";
            $data = [
                "client_id"     => config('services.google.client_id'),
                "client_secret" => config('services.google.client_secret'),
                "refresh_token" => $this->google_refresh_token,
                "grant_type"    => 'refresh_token'
            ];

            $ch = curl_init($url);

            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
            $result = curl_exec($ch);
            $err    = curl_error($ch);

            curl_close($ch);

            if ($err) {
                return $token;
            }
            $result = json_decode($result, true);

            $this->google_token     = isset($result['access_token']) ? $result['access_token'] : "need_to_refresh";
            $this->token_expires_at = isset($result['expires_in']) ? Carbon::now()->addSeconds($result['expires_in']) : Carbon::now();
            $this->save();

            return $this->google_token;

        }

        return $token;
    }
Buffoon answered 10/12, 2019 at 20:18 Comment(0)
L
2
return Socialite::driver('google')
    ->scopes() 
    ->with(["access_type" => "offline", "prompt" => "consent select_account"])
    ->redirect();

By default refresh_token is returned only on first authorization, by adding "prompt" => "consent select_account" we force it to be returned every time.

Lainelainey answered 20/8, 2018 at 14:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.