diff --git a/src/Two/AbstractProvider.php b/src/Two/AbstractProvider.php index 236b75fa..e70d4c29 100644 --- a/src/Two/AbstractProvider.php +++ b/src/Two/AbstractProvider.php @@ -340,6 +340,43 @@ protected function getTokenFields($code) return $fields; } + /** + * Refresh a user's access token with a refresh token. + * + * @param string $refreshToken + * @return \Laravel\Socialite\Two\Token + */ + public function refreshToken($refreshToken) + { + $response = $this->getRefreshTokenResponse($refreshToken); + + return new Token( + Arr::get($response, 'access_token'), + Arr::get($response, 'refresh_token'), + Arr::get($response, 'expires_in'), + explode($this->scopeSeparator, Arr::get($response, 'scope', '')) + ); + } + + /** + * Get the refresh token response for the given refresh token. + * + * @param string $refreshToken + * @return array + */ + protected function getRefreshTokenResponse($refreshToken) + { + return json_decode($this->getHttpClient()->post($this->getTokenUrl(), [ + RequestOptions::HEADERS => ['Accept' => 'application/json'], + RequestOptions::FORM_PARAMS => [ + 'grant_type' => 'refresh_token', + 'refresh_token' => $refreshToken, + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + ], + ])->getBody(), true); + } + /** * Get the code from the request. * diff --git a/src/Two/Token.php b/src/Two/Token.php new file mode 100644 index 00000000..c6af8447 --- /dev/null +++ b/src/Two/Token.php @@ -0,0 +1,50 @@ +token = $token; + $this->refreshToken = $refreshToken; + $this->expiresIn = $expiresIn; + $this->approvedScopes = $approvedScopes; + } +} diff --git a/src/Two/TwitterProvider.php b/src/Two/TwitterProvider.php index b6661f71..98105918 100644 --- a/src/Two/TwitterProvider.php +++ b/src/Two/TwitterProvider.php @@ -91,6 +91,24 @@ public function getAccessTokenResponse($code) return json_decode($response->getBody(), true); } + /** + * {@inheritdoc} + */ + protected function getRefreshTokenResponse($refreshToken) + { + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + RequestOptions::HEADERS => ['Accept' => 'application/json'], + RequestOptions::AUTH => [$this->clientId, $this->clientSecret], + RequestOptions::FORM_PARAMS => [ + 'grant_type' => 'refresh_token', + 'refresh_token' => $refreshToken, + 'client_id' => $this->clientId, + ], + ]); + + return json_decode($response->getBody(), true); + } + /** * {@inheritdoc} */ diff --git a/tests/OAuthTwoTest.php b/tests/OAuthTwoTest.php index 972a2a4d..5c931c94 100644 --- a/tests/OAuthTwoTest.php +++ b/tests/OAuthTwoTest.php @@ -10,6 +10,7 @@ use Laravel\Socialite\Tests\Fixtures\OAuthTwoTestProviderStub; use Laravel\Socialite\Tests\Fixtures\OAuthTwoWithPKCETestProviderStub; use Laravel\Socialite\Two\InvalidStateException; +use Laravel\Socialite\Two\Token; use Laravel\Socialite\Two\User; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -178,4 +179,23 @@ public function testExceptionIsThrownIfStateIsNotSet() $provider = new OAuthTwoTestProviderStub($request, 'client_id', 'client_secret', 'redirect'); $provider->user(); } + + public function testUserRefreshesToken() + { + $request = Request::create('/'); + $provider = new OAuthTwoTestProviderStub($request, 'client_id', 'client_secret', 'redirect_uri'); + $provider->http = m::mock(stdClass::class); + $provider->http->expects('post')->with('http://token.url', [ + 'headers' => ['Accept' => 'application/json'], + 'form_params' => ['grant_type' => 'refresh_token', 'client_id' => 'client_id', 'client_secret' => 'client_secret', 'refresh_token' => 'refresh_token'], + ])->andReturns($response = m::mock(stdClass::class)); + $response->expects('getBody')->andReturns('{ "access_token" : "access_token", "refresh_token" : "refresh_token", "expires_in" : 3600, "scope" : "scope1,scope2" }'); + $token = $provider->refreshToken('refresh_token'); + + $this->assertInstanceOf(Token::class, $token); + $this->assertSame('access_token', $token->token); + $this->assertSame('refresh_token', $token->refreshToken); + $this->assertSame(3600, $token->expiresIn); + $this->assertSame(['scope1', 'scope2'], $token->approvedScopes); + } }