├── LICENSE.md
├── README.md
├── composer.json
└── src
├── AbstractUser.php
├── Contracts
├── Factory.php
├── Provider.php
└── User.php
├── Exceptions
└── DriverMissingConfigurationException.php
├── Facades
└── Socialite.php
├── One
├── AbstractProvider.php
├── MissingTemporaryCredentialsException.php
├── MissingVerifierException.php
├── TwitterProvider.php
└── User.php
├── SocialiteManager.php
├── SocialiteServiceProvider.php
└── Two
├── AbstractProvider.php
├── BitbucketProvider.php
├── FacebookProvider.php
├── GithubProvider.php
├── GitlabProvider.php
├── GoogleProvider.php
├── InvalidStateException.php
├── LinkedInOpenIdProvider.php
├── LinkedInProvider.php
├── ProviderInterface.php
├── SlackOpenIdProvider.php
├── SlackProvider.php
├── Token.php
├── TwitchProvider.php
├── TwitterProvider.php
├── User.php
└── XProvider.php
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Taylor Otwell
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ## Introduction
11 |
12 | Laravel Socialite provides an expressive, fluent interface to OAuth authentication with Bitbucket, Facebook, GitHub, GitLab, Google, LinkedIn, Slack, Twitch, and X. It handles almost all of the boilerplate social authentication code you are dreading writing.
13 |
14 | **We are not accepting new adapters.**
15 |
16 | Adapters for other platforms are listed at the community driven [Socialite Providers](https://socialiteproviders.com/) website.
17 |
18 | ## Official Documentation
19 |
20 | Documentation for Socialite can be found on the [Laravel website](https://laravel.com/docs/socialite).
21 |
22 | ## Contributing
23 |
24 | Thank you for considering contributing to Socialite! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
25 |
26 | ## Code of Conduct
27 |
28 | In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
29 |
30 | ## Security Vulnerabilities
31 |
32 | Please review [our security policy](https://github.com/laravel/socialite/security/policy) on how to report security vulnerabilities.
33 |
34 | ## License
35 |
36 | Laravel Socialite is open-sourced software licensed under the [MIT license](LICENSE.md).
37 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/socialite",
3 | "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.",
4 | "keywords": ["oauth", "laravel"],
5 | "license": "MIT",
6 | "homepage": "https://laravel.com",
7 | "support": {
8 | "issues": "https://github.com/laravel/socialite/issues",
9 | "source": "https://github.com/laravel/socialite"
10 | },
11 | "authors": [
12 | {
13 | "name": "Taylor Otwell",
14 | "email": "taylor@laravel.com"
15 | }
16 | ],
17 | "require": {
18 | "php": "^7.2|^8.0",
19 | "ext-json": "*",
20 | "firebase/php-jwt": "^6.4",
21 | "guzzlehttp/guzzle": "^6.0|^7.0",
22 | "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
23 | "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
24 | "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
25 | "league/oauth1-client": "^1.11",
26 | "phpseclib/phpseclib": "^3.0"
27 | },
28 | "require-dev": {
29 | "mockery/mockery": "^1.0",
30 | "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0",
31 | "phpstan/phpstan": "^1.12.23",
32 | "phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5"
33 | },
34 | "autoload": {
35 | "psr-4": {
36 | "Laravel\\Socialite\\": "src/"
37 | }
38 | },
39 | "autoload-dev": {
40 | "psr-4": {
41 | "Laravel\\Socialite\\Tests\\": "tests/"
42 | }
43 | },
44 | "extra": {
45 | "branch-alias": {
46 | "dev-master": "5.x-dev"
47 | },
48 | "laravel": {
49 | "providers": [
50 | "Laravel\\Socialite\\SocialiteServiceProvider"
51 | ],
52 | "aliases": {
53 | "Socialite": "Laravel\\Socialite\\Facades\\Socialite"
54 | }
55 | }
56 | },
57 | "config": {
58 | "sort-packages": true
59 | },
60 | "minimum-stability": "dev",
61 | "prefer-stable": true
62 | }
63 |
--------------------------------------------------------------------------------
/src/AbstractUser.php:
--------------------------------------------------------------------------------
1 | id;
67 | }
68 |
69 | /**
70 | * Get the nickname / username for the user.
71 | *
72 | * @return string|null
73 | */
74 | public function getNickname()
75 | {
76 | return $this->nickname;
77 | }
78 |
79 | /**
80 | * Get the full name of the user.
81 | *
82 | * @return string|null
83 | */
84 | public function getName()
85 | {
86 | return $this->name;
87 | }
88 |
89 | /**
90 | * Get the e-mail address of the user.
91 | *
92 | * @return string|null
93 | */
94 | public function getEmail()
95 | {
96 | return $this->email;
97 | }
98 |
99 | /**
100 | * Get the avatar / image URL for the user.
101 | *
102 | * @return string|null
103 | */
104 | public function getAvatar()
105 | {
106 | return $this->avatar;
107 | }
108 |
109 | /**
110 | * Get the raw user array.
111 | *
112 | * @return array
113 | */
114 | public function getRaw()
115 | {
116 | return $this->user;
117 | }
118 |
119 | /**
120 | * Set the raw user array from the provider.
121 | *
122 | * @param array $user
123 | * @return $this
124 | */
125 | public function setRaw(array $user)
126 | {
127 | $this->user = $user;
128 |
129 | return $this;
130 | }
131 |
132 | /**
133 | * Map the given array onto the user's properties.
134 | *
135 | * @param array $attributes
136 | * @return $this
137 | */
138 | public function map(array $attributes)
139 | {
140 | $this->attributes = $attributes;
141 |
142 | foreach ($attributes as $key => $value) {
143 | if (property_exists($this, $key)) {
144 | $this->{$key} = $value;
145 | }
146 | }
147 |
148 | return $this;
149 | }
150 |
151 | /**
152 | * Determine if the given raw user attribute exists.
153 | *
154 | * @param string $offset
155 | * @return bool
156 | */
157 | #[\ReturnTypeWillChange]
158 | public function offsetExists($offset)
159 | {
160 | return array_key_exists($offset, $this->user);
161 | }
162 |
163 | /**
164 | * Get the given key from the raw user.
165 | *
166 | * @param string $offset
167 | * @return mixed
168 | */
169 | #[\ReturnTypeWillChange]
170 | public function offsetGet($offset)
171 | {
172 | return $this->user[$offset];
173 | }
174 |
175 | /**
176 | * Set the given attribute on the raw user array.
177 | *
178 | * @param string $offset
179 | * @param mixed $value
180 | * @return void
181 | */
182 | #[\ReturnTypeWillChange]
183 | public function offsetSet($offset, $value)
184 | {
185 | $this->user[$offset] = $value;
186 | }
187 |
188 | /**
189 | * Unset the given value from the raw user array.
190 | *
191 | * @param string $offset
192 | * @return void
193 | */
194 | #[\ReturnTypeWillChange]
195 | public function offsetUnset($offset)
196 | {
197 | unset($this->user[$offset]);
198 | }
199 |
200 | /**
201 | * Get a user attribute value dynamically.
202 | *
203 | * @param string $key
204 | * @return void
205 | */
206 | public function __get($key)
207 | {
208 | return $this->attributes[$key] ?? null;
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/Contracts/Factory.php:
--------------------------------------------------------------------------------
1 | $keys
14 | * @return static
15 | */
16 | public static function make($provider, $keys)
17 | {
18 | /** @phpstan-ignore new.static */
19 | return new static('Missing required configuration keys ['.implode(', ', $keys)."] for [{$provider}] OAuth provider.");
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Facades/Socialite.php:
--------------------------------------------------------------------------------
1 | server = $server;
44 | $this->request = $request;
45 | }
46 |
47 | /**
48 | * Redirect the user to the authentication page for the provider.
49 | *
50 | * @return \Illuminate\Http\RedirectResponse
51 | */
52 | public function redirect()
53 | {
54 | $this->request->session()->put(
55 | 'oauth.temp', $temp = $this->server->getTemporaryCredentials()
56 | );
57 |
58 | return new RedirectResponse($this->server->getAuthorizationUrl($temp));
59 | }
60 |
61 | /**
62 | * Get the User instance for the authenticated user.
63 | *
64 | * @return \Laravel\Socialite\One\User
65 | *
66 | * @throws \Laravel\Socialite\One\MissingVerifierException
67 | */
68 | public function user()
69 | {
70 | if (! $this->hasNecessaryVerifier()) {
71 | throw new MissingVerifierException('Invalid request. Missing OAuth verifier.');
72 | }
73 |
74 | $token = $this->getToken();
75 |
76 | $user = $this->server->getUserDetails(
77 | $token, $this->shouldBypassCache($token->getIdentifier(), $token->getSecret())
78 | );
79 |
80 | $instance = (new User)->setRaw($user->extra)
81 | ->setToken($token->getIdentifier(), $token->getSecret());
82 |
83 | return $instance->map([
84 | 'id' => $user->uid,
85 | 'nickname' => $user->nickname,
86 | 'name' => $user->name,
87 | 'email' => $user->email,
88 | 'avatar' => $user->imageUrl,
89 | ]);
90 | }
91 |
92 | /**
93 | * Get a Social User instance from a known access token and secret.
94 | *
95 | * @param string $token
96 | * @param string $secret
97 | * @return \Laravel\Socialite\One\User
98 | */
99 | public function userFromTokenAndSecret($token, $secret)
100 | {
101 | $tokenCredentials = new TokenCredentials();
102 |
103 | $tokenCredentials->setIdentifier($token);
104 | $tokenCredentials->setSecret($secret);
105 |
106 | $user = $this->server->getUserDetails(
107 | $tokenCredentials, $this->shouldBypassCache($token, $secret)
108 | );
109 |
110 | $instance = (new User)->setRaw($user->extra)
111 | ->setToken($tokenCredentials->getIdentifier(), $tokenCredentials->getSecret());
112 |
113 | return $instance->map([
114 | 'id' => $user->uid,
115 | 'nickname' => $user->nickname,
116 | 'name' => $user->name,
117 | 'email' => $user->email,
118 | 'avatar' => $user->imageUrl,
119 | ]);
120 | }
121 |
122 | /**
123 | * Get the token credentials for the request.
124 | *
125 | * @return \League\OAuth1\Client\Credentials\TokenCredentials
126 | */
127 | protected function getToken()
128 | {
129 | $temp = $this->request->session()->get('oauth.temp');
130 |
131 | if (! $temp) {
132 | throw new MissingTemporaryCredentialsException('Missing temporary OAuth credentials.');
133 | }
134 |
135 | return $this->server->getTokenCredentials(
136 | $temp, $this->request->get('oauth_token'), $this->request->get('oauth_verifier')
137 | );
138 | }
139 |
140 | /**
141 | * Determine if the request has the necessary OAuth verifier.
142 | *
143 | * @return bool
144 | */
145 | protected function hasNecessaryVerifier()
146 | {
147 | return $this->request->has(['oauth_token', 'oauth_verifier']);
148 | }
149 |
150 | /**
151 | * Determine if the user information cache should be bypassed.
152 | *
153 | * @param string $token
154 | * @param string $secret
155 | * @return bool
156 | */
157 | protected function shouldBypassCache($token, $secret)
158 | {
159 | $newHash = sha1($token.'_'.$secret);
160 |
161 | if (! empty($this->userHash) && $newHash !== $this->userHash) {
162 | $this->userHash = $newHash;
163 |
164 | return true;
165 | }
166 |
167 | $this->userHash = $this->userHash ?: $newHash;
168 |
169 | return false;
170 | }
171 |
172 | /**
173 | * Set the request instance.
174 | *
175 | * @param \Illuminate\Http\Request $request
176 | * @return $this
177 | */
178 | public function setRequest(Request $request)
179 | {
180 | $this->request = $request;
181 |
182 | return $this;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/One/MissingTemporaryCredentialsException.php:
--------------------------------------------------------------------------------
1 | hasNecessaryVerifier()) {
13 | throw new MissingVerifierException('Invalid request. Missing OAuth verifier.');
14 | }
15 |
16 | $user = $this->server->getUserDetails($token = $this->getToken(), $this->shouldBypassCache($token->getIdentifier(), $token->getSecret()));
17 |
18 | $extraDetails = [
19 | 'location' => $user->location,
20 | 'description' => $user->description,
21 | ];
22 |
23 | $instance = (new User)->setRaw(array_merge($user->extra, $user->urls, $extraDetails))
24 | ->setToken($token->getIdentifier(), $token->getSecret());
25 |
26 | return $instance->map([
27 | 'id' => $user->uid,
28 | 'nickname' => $user->nickname,
29 | 'name' => $user->name,
30 | 'email' => $user->email,
31 | 'avatar' => $user->imageUrl,
32 | 'avatar_original' => str_replace('_normal', '', $user->imageUrl),
33 | ]);
34 | }
35 |
36 | /**
37 | * Set the access level the application should request to the user account.
38 | *
39 | * @param string $scope
40 | * @return void
41 | */
42 | public function scope(string $scope)
43 | {
44 | $this->server->setApplicationScope($scope);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/One/User.php:
--------------------------------------------------------------------------------
1 | token = $token;
33 | $this->tokenSecret = $tokenSecret;
34 |
35 | return $this;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/SocialiteManager.php:
--------------------------------------------------------------------------------
1 | driver($driver);
45 | }
46 |
47 | /**
48 | * Create an instance of the specified driver.
49 | *
50 | * @return \Laravel\Socialite\Two\AbstractProvider
51 | */
52 | protected function createGithubDriver()
53 | {
54 | $config = $this->config->get('services.github');
55 |
56 | return $this->buildProvider(
57 | GithubProvider::class, $config
58 | );
59 | }
60 |
61 | /**
62 | * Create an instance of the specified driver.
63 | *
64 | * @return \Laravel\Socialite\Two\AbstractProvider
65 | */
66 | protected function createFacebookDriver()
67 | {
68 | $config = $this->config->get('services.facebook');
69 |
70 | return $this->buildProvider(
71 | FacebookProvider::class, $config
72 | );
73 | }
74 |
75 | /**
76 | * Create an instance of the specified driver.
77 | *
78 | * @return \Laravel\Socialite\Two\AbstractProvider
79 | */
80 | protected function createGoogleDriver()
81 | {
82 | $config = $this->config->get('services.google');
83 |
84 | return $this->buildProvider(
85 | GoogleProvider::class, $config
86 | );
87 | }
88 |
89 | /**
90 | * Create an instance of the specified driver.
91 | *
92 | * @return \Laravel\Socialite\Two\AbstractProvider
93 | */
94 | protected function createLinkedinDriver()
95 | {
96 | $config = $this->config->get('services.linkedin');
97 |
98 | return $this->buildProvider(
99 | LinkedInProvider::class, $config
100 | );
101 | }
102 |
103 | /**
104 | * Create an instance of the specified driver.
105 | *
106 | * @return \Laravel\Socialite\Two\AbstractProvider
107 | */
108 | protected function createLinkedinOpenidDriver()
109 | {
110 | $config = $this->config->get('services.linkedin-openid');
111 |
112 | return $this->buildProvider(
113 | LinkedInOpenIdProvider::class, $config
114 | );
115 | }
116 |
117 | /**
118 | * Create an instance of the specified driver.
119 | *
120 | * @return \Laravel\Socialite\Two\AbstractProvider
121 | */
122 | protected function createBitbucketDriver()
123 | {
124 | $config = $this->config->get('services.bitbucket');
125 |
126 | return $this->buildProvider(
127 | BitbucketProvider::class, $config
128 | );
129 | }
130 |
131 | /**
132 | * Create an instance of the specified driver.
133 | *
134 | * @return \Laravel\Socialite\Two\AbstractProvider
135 | */
136 | protected function createGitlabDriver()
137 | {
138 | $config = $this->config->get('services.gitlab');
139 |
140 | return $this->buildProvider(
141 | GitlabProvider::class, $config
142 | )->setHost($config['host'] ?? null);
143 | }
144 |
145 | /**
146 | * Create an instance of the specified driver.
147 | *
148 | * @return \Laravel\Socialite\One\AbstractProvider|\Laravel\Socialite\Two\AbstractProvider
149 | */
150 | protected function createTwitterDriver()
151 | {
152 | $config = $this->config->get('services.twitter');
153 |
154 | if (($config['oauth'] ?? null) === 2) {
155 | return $this->createTwitterOAuth2Driver();
156 | }
157 |
158 | return new TwitterProvider(
159 | $this->container->make('request'), new TwitterServer($this->formatConfig($config))
160 | );
161 | }
162 |
163 | /**
164 | * Create an instance of the specified driver.
165 | *
166 | * @return \Laravel\Socialite\Two\AbstractProvider
167 | */
168 | protected function createTwitterOAuth2Driver()
169 | {
170 | $config = $this->config->get('services.twitter') ?? $this->config->get('services.twitter-oauth-2');
171 |
172 | return $this->buildProvider(
173 | TwitterOAuth2Provider::class, $config
174 | );
175 | }
176 |
177 | /**
178 | * Create an instance of the specified driver.
179 | *
180 | * @return \Laravel\Socialite\Two\AbstractProvider
181 | */
182 | protected function createXDriver()
183 | {
184 | $config = $this->config->get('services.x') ?? $this->config->get('services.x-oauth-2');
185 |
186 | return $this->buildProvider(
187 | XProvider::class, $config
188 | );
189 | }
190 |
191 | /**
192 | * Create an instance of the specified driver.
193 | *
194 | * @return \Laravel\Socialite\Two\AbstractProvider
195 | */
196 | protected function createTwitchDriver()
197 | {
198 | $config = $this->config->get('services.twitch');
199 |
200 | return $this->buildProvider(
201 | TwitchProvider::class, $config
202 | );
203 | }
204 |
205 | /**
206 | * Create an instance of the specified driver.
207 | *
208 | * @return \Laravel\Socialite\Two\AbstractProvider
209 | */
210 | protected function createSlackDriver()
211 | {
212 | $config = $this->config->get('services.slack');
213 |
214 | return $this->buildProvider(
215 | SlackProvider::class, $config
216 | );
217 | }
218 |
219 | /**
220 | * Create an instance of the specified driver.
221 | *
222 | * @return \Laravel\Socialite\Two\AbstractProvider
223 | */
224 | protected function createSlackOpenidDriver()
225 | {
226 | $config = $this->config->get('services.slack-openid');
227 |
228 | return $this->buildProvider(
229 | SlackOpenIdProvider::class, $config
230 | );
231 | }
232 |
233 | /**
234 | * Build an OAuth 2 provider instance.
235 | *
236 | * @param string $provider
237 | * @param array $config
238 | * @return \Laravel\Socialite\Two\AbstractProvider
239 | */
240 | public function buildProvider($provider, $config)
241 | {
242 | $requiredKeys = ['client_id', 'client_secret', 'redirect'];
243 |
244 | $missingKeys = array_diff($requiredKeys, array_keys($config ?? []));
245 |
246 | if (! empty($missingKeys)) {
247 | throw DriverMissingConfigurationException::make($provider, $missingKeys);
248 | }
249 |
250 | return (new $provider(
251 | $this->container->make('request'), $config['client_id'],
252 | $config['client_secret'], $this->formatRedirectUrl($config),
253 | Arr::get($config, 'guzzle', [])
254 | ))->scopes($config['scopes'] ?? []);
255 | }
256 |
257 | /**
258 | * Format the server configuration.
259 | *
260 | * @param array $config
261 | * @return array
262 | */
263 | public function formatConfig(array $config)
264 | {
265 | return array_merge([
266 | 'identifier' => $config['client_id'],
267 | 'secret' => $config['client_secret'],
268 | 'callback_uri' => $this->formatRedirectUrl($config),
269 | ], $config);
270 | }
271 |
272 | /**
273 | * Format the callback URL, resolving a relative URI if needed.
274 | *
275 | * @param array $config
276 | * @return string
277 | */
278 | protected function formatRedirectUrl(array $config)
279 | {
280 | $redirect = value($config['redirect']);
281 |
282 | return Str::startsWith($redirect ?? '', '/')
283 | ? $this->container->make('url')->to($redirect)
284 | : $redirect;
285 | }
286 |
287 | /**
288 | * Forget all of the resolved driver instances.
289 | *
290 | * @return $this
291 | */
292 | public function forgetDrivers()
293 | {
294 | $this->drivers = [];
295 |
296 | return $this;
297 | }
298 |
299 | /**
300 | * Set the container instance used by the manager.
301 | *
302 | * @param \Illuminate\Contracts\Container\Container $container
303 | * @return $this
304 | */
305 | public function setContainer($container)
306 | {
307 | $this->app = $container;
308 | $this->container = $container;
309 | $this->config = $container->make('config');
310 |
311 | return $this;
312 | }
313 |
314 | /**
315 | * Get the default driver name.
316 | *
317 | * @return string
318 | *
319 | * @throws \InvalidArgumentException
320 | */
321 | public function getDefaultDriver()
322 | {
323 | throw new InvalidArgumentException('No Socialite driver was specified.');
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/src/SocialiteServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton(Factory::class, function ($app) {
19 | return new SocialiteManager($app);
20 | });
21 | }
22 |
23 | /**
24 | * Get the services provided by the provider.
25 | *
26 | * @return array
27 | */
28 | public function provides()
29 | {
30 | return [Factory::class];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Two/AbstractProvider.php:
--------------------------------------------------------------------------------
1 | guzzle = $guzzle;
119 | $this->request = $request;
120 | $this->clientId = $clientId;
121 | $this->redirectUrl = $redirectUrl;
122 | $this->clientSecret = $clientSecret;
123 | }
124 |
125 | /**
126 | * Get the authentication URL for the provider.
127 | *
128 | * @param string $state
129 | * @return string
130 | */
131 | abstract protected function getAuthUrl($state);
132 |
133 | /**
134 | * Get the token URL for the provider.
135 | *
136 | * @return string
137 | */
138 | abstract protected function getTokenUrl();
139 |
140 | /**
141 | * Get the raw user for the given access token.
142 | *
143 | * @param string $token
144 | * @return mixed
145 | */
146 | abstract protected function getUserByToken($token);
147 |
148 | /**
149 | * Map the raw user array to a Socialite User instance.
150 | *
151 | * @param array $user
152 | * @return \Laravel\Socialite\Two\User
153 | */
154 | abstract protected function mapUserToObject(array $user);
155 |
156 | /**
157 | * Redirect the user of the application to the provider's authentication screen.
158 | *
159 | * @return \Illuminate\Http\RedirectResponse
160 | */
161 | public function redirect()
162 | {
163 | $state = null;
164 |
165 | if ($this->usesState()) {
166 | $this->request->session()->put('state', $state = $this->getState());
167 | }
168 |
169 | if ($this->usesPKCE()) {
170 | $this->request->session()->put('code_verifier', $this->getCodeVerifier());
171 | }
172 |
173 | return new RedirectResponse($this->getAuthUrl($state));
174 | }
175 |
176 | /**
177 | * Build the authentication URL for the provider from the given base URL.
178 | *
179 | * @param string $url
180 | * @param string $state
181 | * @return string
182 | */
183 | protected function buildAuthUrlFromBase($url, $state)
184 | {
185 | return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
186 | }
187 |
188 | /**
189 | * Get the GET parameters for the code request.
190 | *
191 | * @param string|null $state
192 | * @return array
193 | */
194 | protected function getCodeFields($state = null)
195 | {
196 | $fields = [
197 | 'client_id' => $this->clientId,
198 | 'redirect_uri' => $this->redirectUrl,
199 | 'scope' => $this->formatScopes($this->getScopes(), $this->scopeSeparator),
200 | 'response_type' => 'code',
201 | ];
202 |
203 | if ($this->usesState()) {
204 | $fields['state'] = $state;
205 | }
206 |
207 | if ($this->usesPKCE()) {
208 | $fields['code_challenge'] = $this->getCodeChallenge();
209 | $fields['code_challenge_method'] = $this->getCodeChallengeMethod();
210 | }
211 |
212 | return array_merge($fields, $this->parameters);
213 | }
214 |
215 | /**
216 | * Format the given scopes.
217 | *
218 | * @param array $scopes
219 | * @param string $scopeSeparator
220 | * @return string
221 | */
222 | protected function formatScopes(array $scopes, $scopeSeparator)
223 | {
224 | return implode($scopeSeparator, $scopes);
225 | }
226 |
227 | /**
228 | * {@inheritdoc}
229 | */
230 | public function user()
231 | {
232 | if ($this->user) {
233 | return $this->user;
234 | }
235 |
236 | if ($this->hasInvalidState()) {
237 | throw new InvalidStateException;
238 | }
239 |
240 | $response = $this->getAccessTokenResponse($this->getCode());
241 |
242 | $user = $this->getUserByToken(Arr::get($response, 'access_token'));
243 |
244 | return $this->userInstance($response, $user);
245 | }
246 |
247 | /**
248 | * Create a user instance from the given data.
249 | *
250 | * @param array $response
251 | * @param array $user
252 | * @return \Laravel\Socialite\Two\User
253 | */
254 | protected function userInstance(array $response, array $user)
255 | {
256 | $this->user = $this->mapUserToObject($user);
257 |
258 | return $this->user->setToken(Arr::get($response, 'access_token'))
259 | ->setRefreshToken(Arr::get($response, 'refresh_token'))
260 | ->setExpiresIn(Arr::get($response, 'expires_in'))
261 | ->setApprovedScopes(explode($this->scopeSeparator, Arr::get($response, 'scope', '')));
262 | }
263 |
264 | /**
265 | * Get a Social User instance from a known access token.
266 | *
267 | * @param string $token
268 | * @return \Laravel\Socialite\Two\User
269 | */
270 | public function userFromToken($token)
271 | {
272 | $user = $this->mapUserToObject($this->getUserByToken($token));
273 |
274 | return $user->setToken($token);
275 | }
276 |
277 | /**
278 | * Determine if the current request / session has a mismatching "state".
279 | *
280 | * @return bool
281 | */
282 | protected function hasInvalidState()
283 | {
284 | if ($this->isStateless()) {
285 | return false;
286 | }
287 |
288 | $state = $this->request->session()->pull('state');
289 |
290 | return empty($state) || $this->request->input('state') !== $state;
291 | }
292 |
293 | /**
294 | * Get the access token response for the given code.
295 | *
296 | * @param string $code
297 | * @return mixed
298 | */
299 | public function getAccessTokenResponse($code)
300 | {
301 | $response = $this->getHttpClient()->post($this->getTokenUrl(), [
302 | RequestOptions::HEADERS => $this->getTokenHeaders($code),
303 | RequestOptions::FORM_PARAMS => $this->getTokenFields($code),
304 | ]);
305 |
306 | return json_decode($response->getBody(), true);
307 | }
308 |
309 | /**
310 | * Get the headers for the access token request.
311 | *
312 | * @param string $code
313 | * @return array
314 | */
315 | protected function getTokenHeaders($code)
316 | {
317 | return ['Accept' => 'application/json'];
318 | }
319 |
320 | /**
321 | * Get the POST fields for the token request.
322 | *
323 | * @param string $code
324 | * @return array
325 | */
326 | protected function getTokenFields($code)
327 | {
328 | $fields = [
329 | 'grant_type' => 'authorization_code',
330 | 'client_id' => $this->clientId,
331 | 'client_secret' => $this->clientSecret,
332 | 'code' => $code,
333 | 'redirect_uri' => $this->redirectUrl,
334 | ];
335 |
336 | if ($this->usesPKCE()) {
337 | $fields['code_verifier'] = $this->request->session()->pull('code_verifier');
338 | }
339 |
340 | return array_merge($fields, $this->parameters);
341 | }
342 |
343 | /**
344 | * Refresh a user's access token with a refresh token.
345 | *
346 | * @param string $refreshToken
347 | * @return \Laravel\Socialite\Two\Token
348 | */
349 | public function refreshToken($refreshToken)
350 | {
351 | $response = $this->getRefreshTokenResponse($refreshToken);
352 |
353 | return new Token(
354 | Arr::get($response, 'access_token'),
355 | Arr::get($response, 'refresh_token'),
356 | Arr::get($response, 'expires_in'),
357 | explode($this->scopeSeparator, Arr::get($response, 'scope', ''))
358 | );
359 | }
360 |
361 | /**
362 | * Get the refresh token response for the given refresh token.
363 | *
364 | * @param string $refreshToken
365 | * @return mixed
366 | */
367 | protected function getRefreshTokenResponse($refreshToken)
368 | {
369 | return json_decode($this->getHttpClient()->post($this->getTokenUrl(), [
370 | RequestOptions::HEADERS => ['Accept' => 'application/json'],
371 | RequestOptions::FORM_PARAMS => [
372 | 'grant_type' => 'refresh_token',
373 | 'refresh_token' => $refreshToken,
374 | 'client_id' => $this->clientId,
375 | 'client_secret' => $this->clientSecret,
376 | ],
377 | ])->getBody(), true);
378 | }
379 |
380 | /**
381 | * Get the code from the request.
382 | *
383 | * @return string
384 | */
385 | protected function getCode()
386 | {
387 | return $this->request->input('code');
388 | }
389 |
390 | /**
391 | * Merge the scopes of the requested access.
392 | *
393 | * @param array|string $scopes
394 | * @return $this
395 | */
396 | public function scopes($scopes)
397 | {
398 | $this->scopes = array_values(array_unique(array_merge($this->scopes, (array) $scopes)));
399 |
400 | return $this;
401 | }
402 |
403 | /**
404 | * Set the scopes of the requested access.
405 | *
406 | * @param array|string $scopes
407 | * @return $this
408 | */
409 | public function setScopes($scopes)
410 | {
411 | $this->scopes = array_values(array_unique((array) $scopes));
412 |
413 | return $this;
414 | }
415 |
416 | /**
417 | * Get the current scopes.
418 | *
419 | * @return array
420 | */
421 | public function getScopes()
422 | {
423 | return $this->scopes;
424 | }
425 |
426 | /**
427 | * Set the redirect URL.
428 | *
429 | * @param string $url
430 | * @return $this
431 | */
432 | public function redirectUrl($url)
433 | {
434 | $this->redirectUrl = $url;
435 |
436 | return $this;
437 | }
438 |
439 | /**
440 | * Get a instance of the Guzzle HTTP client.
441 | *
442 | * @return \GuzzleHttp\Client
443 | */
444 | protected function getHttpClient()
445 | {
446 | if (is_null($this->httpClient)) {
447 | $this->httpClient = new Client($this->guzzle);
448 | }
449 |
450 | return $this->httpClient;
451 | }
452 |
453 | /**
454 | * Set the Guzzle HTTP client instance.
455 | *
456 | * @param \GuzzleHttp\Client $client
457 | * @return $this
458 | */
459 | public function setHttpClient(Client $client)
460 | {
461 | $this->httpClient = $client;
462 |
463 | return $this;
464 | }
465 |
466 | /**
467 | * Set the request instance.
468 | *
469 | * @param \Illuminate\Http\Request $request
470 | * @return $this
471 | */
472 | public function setRequest(Request $request)
473 | {
474 | $this->request = $request;
475 |
476 | return $this;
477 | }
478 |
479 | /**
480 | * Determine if the provider is operating with state.
481 | *
482 | * @return bool
483 | */
484 | protected function usesState()
485 | {
486 | return ! $this->stateless;
487 | }
488 |
489 | /**
490 | * Determine if the provider is operating as stateless.
491 | *
492 | * @return bool
493 | */
494 | protected function isStateless()
495 | {
496 | return $this->stateless;
497 | }
498 |
499 | /**
500 | * Indicates that the provider should operate as stateless.
501 | *
502 | * @return $this
503 | */
504 | public function stateless()
505 | {
506 | $this->stateless = true;
507 |
508 | return $this;
509 | }
510 |
511 | /**
512 | * Get the string used for session state.
513 | *
514 | * @return string
515 | */
516 | protected function getState()
517 | {
518 | return Str::random(40);
519 | }
520 |
521 | /**
522 | * Determine if the provider uses PKCE.
523 | *
524 | * @return bool
525 | */
526 | protected function usesPKCE()
527 | {
528 | return $this->usesPKCE;
529 | }
530 |
531 | /**
532 | * Enables PKCE for the provider.
533 | *
534 | * @return $this
535 | */
536 | public function enablePKCE()
537 | {
538 | $this->usesPKCE = true;
539 |
540 | return $this;
541 | }
542 |
543 | /**
544 | * Generates a random string of the right length for the PKCE code verifier.
545 | *
546 | * @return string
547 | */
548 | protected function getCodeVerifier()
549 | {
550 | return Str::random(96);
551 | }
552 |
553 | /**
554 | * Generates the PKCE code challenge based on the PKCE code verifier in the session.
555 | *
556 | * @return string
557 | */
558 | protected function getCodeChallenge()
559 | {
560 | $hashed = hash('sha256', $this->request->session()->get('code_verifier'), true);
561 |
562 | return rtrim(strtr(base64_encode($hashed), '+/', '-_'), '=');
563 | }
564 |
565 | /**
566 | * Returns the hash method used to calculate the PKCE code challenge.
567 | *
568 | * @return string
569 | */
570 | protected function getCodeChallengeMethod()
571 | {
572 | return 'S256';
573 | }
574 |
575 | /**
576 | * Set the custom parameters of the request.
577 | *
578 | * @param array $parameters
579 | * @return $this
580 | */
581 | public function with(array $parameters)
582 | {
583 | $this->parameters = $parameters;
584 |
585 | return $this;
586 | }
587 | }
588 |
--------------------------------------------------------------------------------
/src/Two/BitbucketProvider.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://bitbucket.org/site/oauth2/authorize', $state);
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | protected function getTokenUrl()
37 | {
38 | return 'https://bitbucket.org/site/oauth2/access_token';
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | */
44 | protected function getUserByToken($token)
45 | {
46 | $response = $this->getHttpClient()->get('https://api.bitbucket.org/2.0/user', [
47 | RequestOptions::QUERY => ['access_token' => $token],
48 | ]);
49 |
50 | $user = json_decode($response->getBody(), true);
51 |
52 | if (in_array('email', $this->scopes, true)) {
53 | $user['email'] = $this->getEmailByToken($token);
54 | }
55 |
56 | return $user;
57 | }
58 |
59 | /**
60 | * Get the email for the given access token.
61 | *
62 | * @param string $token
63 | * @return string|null
64 | */
65 | protected function getEmailByToken($token)
66 | {
67 | $emailsUrl = 'https://api.bitbucket.org/2.0/user/emails?access_token='.$token;
68 |
69 | try {
70 | $response = $this->getHttpClient()->get($emailsUrl);
71 | } catch (Exception $e) {
72 | return;
73 | }
74 |
75 | $emails = json_decode($response->getBody(), true);
76 |
77 | foreach ($emails['values'] as $email) {
78 | if ($email['type'] === 'email' && $email['is_primary'] && $email['is_confirmed']) {
79 | return $email['email'];
80 | }
81 | }
82 | }
83 |
84 | /**
85 | * {@inheritdoc}
86 | */
87 | protected function mapUserToObject(array $user)
88 | {
89 | return (new User)->setRaw($user)->map([
90 | 'id' => $user['uuid'],
91 | 'nickname' => $user['username'],
92 | 'name' => Arr::get($user, 'display_name'),
93 | 'email' => Arr::get($user, 'email'),
94 | 'avatar' => Arr::get($user, 'links.avatar.href'),
95 | ]);
96 | }
97 |
98 | /**
99 | * Get the access token for the given code.
100 | *
101 | * @param string $code
102 | * @return string
103 | */
104 | public function getAccessToken($code)
105 | {
106 | $response = $this->getHttpClient()->post($this->getTokenUrl(), [
107 | RequestOptions::AUTH => [$this->clientId, $this->clientSecret],
108 | RequestOptions::HEADERS => ['Accept' => 'application/json'],
109 | RequestOptions::FORM_PARAMS => $this->getTokenFields($code),
110 | ]);
111 |
112 | return json_decode($response->getBody(), true)['access_token'];
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Two/FacebookProvider.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://www.facebook.com/'.$this->version.'/dialog/oauth', $state);
70 | }
71 |
72 | /**
73 | * {@inheritdoc}
74 | */
75 | protected function getTokenUrl()
76 | {
77 | return $this->graphUrl.'/'.$this->version.'/oauth/access_token';
78 | }
79 |
80 | /**
81 | * {@inheritdoc}
82 | */
83 | public function getAccessTokenResponse($code)
84 | {
85 | $response = $this->getHttpClient()->post($this->getTokenUrl(), [
86 | RequestOptions::FORM_PARAMS => $this->getTokenFields($code),
87 | ]);
88 |
89 | $data = json_decode($response->getBody(), true);
90 |
91 | return Arr::add($data, 'expires_in', Arr::pull($data, 'expires'));
92 | }
93 |
94 | /**
95 | * {@inheritdoc}
96 | */
97 | protected function getUserByToken($token)
98 | {
99 | $this->lastToken = $token;
100 |
101 | return $this->getUserByOIDCToken($token) ??
102 | $this->getUserFromAccessToken($token);
103 | }
104 |
105 | /**
106 | * Get user based on the OIDC token.
107 | *
108 | * @param string $token
109 | * @return array
110 | */
111 | protected function getUserByOIDCToken($token)
112 | {
113 | $kid = json_decode(base64_decode(explode('.', $token)[0]), true)['kid'] ?? null;
114 |
115 | if ($kid === null) {
116 | return null;
117 | }
118 |
119 | $data = (array) JWT::decode($token, $this->getPublicKeyOfOIDCToken($kid));
120 |
121 | throw_if($data['aud'] !== $this->clientId, new Exception('Token has incorrect audience.'));
122 | throw_if($data['iss'] !== 'https://www.facebook.com', new Exception('Token has incorrect issuer.'));
123 |
124 | $data['id'] = $data['sub'];
125 |
126 | if (isset($data['given_name'])) {
127 | $data['first_name'] = $data['given_name'];
128 | }
129 |
130 | if (isset($data['family_name'])) {
131 | $data['last_name'] = $data['family_name'];
132 | }
133 |
134 | return $data;
135 | }
136 |
137 | /**
138 | * Get the public key to verify the signature of OIDC token.
139 | *
140 | * @param string $id
141 | * @return \Firebase\JWT\Key
142 | */
143 | protected function getPublicKeyOfOIDCToken(string $kid)
144 | {
145 | $response = $this->getHttpClient()->get('https://limited.facebook.com/.well-known/oauth/openid/jwks/');
146 |
147 | $key = Arr::first(json_decode($response->getBody()->getContents(), true)['keys'], function ($key) use ($kid) {
148 | return $key['kid'] === $kid;
149 | });
150 |
151 | $key['n'] = new BigInteger(JWT::urlsafeB64Decode($key['n']), 256);
152 | $key['e'] = new BigInteger(JWT::urlsafeB64Decode($key['e']), 256);
153 |
154 | return new Key((string) RSA::load($key), 'RS256');
155 | }
156 |
157 | /**
158 | * Get user based on the access token.
159 | *
160 | * @param string $token
161 | * @return array
162 | */
163 | protected function getUserFromAccessToken($token)
164 | {
165 | $params = [
166 | 'access_token' => $token,
167 | 'fields' => implode(',', $this->fields),
168 | ];
169 |
170 | if (! empty($this->clientSecret)) {
171 | $params['appsecret_proof'] = hash_hmac('sha256', $token, $this->clientSecret);
172 | }
173 |
174 | $response = $this->getHttpClient()->get($this->graphUrl.'/'.$this->version.'/me', [
175 | RequestOptions::HEADERS => [
176 | 'Accept' => 'application/json',
177 | ],
178 | RequestOptions::QUERY => $params,
179 | ]);
180 |
181 | return json_decode($response->getBody(), true);
182 | }
183 |
184 | /**
185 | * {@inheritdoc}
186 | */
187 | protected function mapUserToObject(array $user)
188 | {
189 | if (! isset($user['sub'])) {
190 | $avatarUrl = $this->graphUrl.'/'.$this->version.'/'.$user['id'].'/picture';
191 |
192 | $avatarOriginalUrl = $avatarUrl.'?width=1920';
193 | }
194 |
195 | return (new User)->setRaw($user)->map([
196 | 'id' => $user['id'],
197 | 'nickname' => null,
198 | 'name' => $user['name'] ?? null,
199 | 'email' => $user['email'] ?? null,
200 | 'avatar' => $avatarUrl ?? $user['picture'] ?? null,
201 | 'avatar_original' => $avatarOriginalUrl ?? $user['picture'] ?? null,
202 | 'profileUrl' => $user['link'] ?? null,
203 | ]);
204 | }
205 |
206 | /**
207 | * {@inheritdoc}
208 | */
209 | protected function getCodeFields($state = null)
210 | {
211 | $fields = parent::getCodeFields($state);
212 |
213 | if ($this->popup) {
214 | $fields['display'] = 'popup';
215 | }
216 |
217 | if ($this->reRequest) {
218 | $fields['auth_type'] = 'rerequest';
219 | }
220 |
221 | return $fields;
222 | }
223 |
224 | /**
225 | * Set the user fields to request from Facebook.
226 | *
227 | * @param array $fields
228 | * @return $this
229 | */
230 | public function fields(array $fields)
231 | {
232 | $this->fields = $fields;
233 |
234 | return $this;
235 | }
236 |
237 | /**
238 | * Set the dialog to be displayed as a popup.
239 | *
240 | * @return $this
241 | */
242 | public function asPopup()
243 | {
244 | $this->popup = true;
245 |
246 | return $this;
247 | }
248 |
249 | /**
250 | * Re-request permissions which were previously declined.
251 | *
252 | * @return $this
253 | */
254 | public function reRequest()
255 | {
256 | $this->reRequest = true;
257 |
258 | return $this;
259 | }
260 |
261 | /**
262 | * Get the last access token used.
263 | *
264 | * @return string|null
265 | */
266 | public function lastToken()
267 | {
268 | return $this->lastToken;
269 | }
270 |
271 | /**
272 | * Specify which graph version should be used.
273 | *
274 | * @param string $version
275 | * @return $this
276 | */
277 | public function usingGraphVersion(string $version)
278 | {
279 | $this->version = $version;
280 |
281 | return $this;
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/src/Two/GithubProvider.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://github.com/login/oauth/authorize', $state);
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | protected function getTokenUrl()
30 | {
31 | return 'https://github.com/login/oauth/access_token';
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | protected function getUserByToken($token)
38 | {
39 | $userUrl = 'https://api.github.com/user';
40 |
41 | $response = $this->getHttpClient()->get(
42 | $userUrl, $this->getRequestOptions($token)
43 | );
44 |
45 | $user = json_decode($response->getBody(), true);
46 |
47 | if (in_array('user:email', $this->scopes, true)) {
48 | $user['email'] = $this->getEmailByToken($token);
49 | }
50 |
51 | return $user;
52 | }
53 |
54 | /**
55 | * Get the email for the given access token.
56 | *
57 | * @param string $token
58 | * @return string|null
59 | */
60 | protected function getEmailByToken($token)
61 | {
62 | $emailsUrl = 'https://api.github.com/user/emails';
63 |
64 | try {
65 | $response = $this->getHttpClient()->get(
66 | $emailsUrl, $this->getRequestOptions($token)
67 | );
68 | } catch (Exception $e) {
69 | return;
70 | }
71 |
72 | foreach (json_decode($response->getBody(), true) as $email) {
73 | if ($email['primary'] && $email['verified']) {
74 | return $email['email'];
75 | }
76 | }
77 | }
78 |
79 | /**
80 | * {@inheritdoc}
81 | */
82 | protected function mapUserToObject(array $user)
83 | {
84 | return (new User)->setRaw($user)->map([
85 | 'id' => $user['id'],
86 | 'nodeId' => $user['node_id'],
87 | 'nickname' => $user['login'],
88 | 'name' => Arr::get($user, 'name'),
89 | 'email' => Arr::get($user, 'email'),
90 | 'avatar' => $user['avatar_url'],
91 | ]);
92 | }
93 |
94 | /**
95 | * Get the default options for an HTTP request.
96 | *
97 | * @param string $token
98 | * @return array
99 | */
100 | protected function getRequestOptions($token)
101 | {
102 | return [
103 | RequestOptions::HEADERS => [
104 | 'Accept' => 'application/vnd.github.v3+json',
105 | 'Authorization' => 'token '.$token,
106 | ],
107 | ];
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Two/GitlabProvider.php:
--------------------------------------------------------------------------------
1 | host = rtrim($host, '/');
40 | }
41 |
42 | return $this;
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | protected function getAuthUrl($state)
49 | {
50 | return $this->buildAuthUrlFromBase($this->host.'/oauth/authorize', $state);
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | protected function getTokenUrl()
57 | {
58 | return $this->host.'/oauth/token';
59 | }
60 |
61 | /**
62 | * {@inheritdoc}
63 | */
64 | protected function getUserByToken($token)
65 | {
66 | $response = $this->getHttpClient()->get($this->host.'/api/v3/user', [
67 | RequestOptions::QUERY => ['access_token' => $token],
68 | ]);
69 |
70 | return json_decode($response->getBody(), true);
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | protected function mapUserToObject(array $user)
77 | {
78 | return (new User)->setRaw($user)->map([
79 | 'id' => $user['id'],
80 | 'nickname' => $user['username'],
81 | 'name' => $user['name'],
82 | 'email' => $user['email'],
83 | 'avatar' => $user['avatar_url'],
84 | ]);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Two/GoogleProvider.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://accounts.google.com/o/oauth2/auth', $state);
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | protected function getTokenUrl()
40 | {
41 | return 'https://www.googleapis.com/oauth2/v4/token';
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | protected function getUserByToken($token)
48 | {
49 | $response = $this->getHttpClient()->get('https://www.googleapis.com/oauth2/v3/userinfo', [
50 | RequestOptions::QUERY => [
51 | 'prettyPrint' => 'false',
52 | ],
53 | RequestOptions::HEADERS => [
54 | 'Accept' => 'application/json',
55 | 'Authorization' => 'Bearer '.$token,
56 | ],
57 | ]);
58 |
59 | return json_decode((string) $response->getBody(), true);
60 | }
61 |
62 | /**
63 | * {@inheritdoc}
64 | */
65 | public function refreshToken($refreshToken)
66 | {
67 | $response = $this->getRefreshTokenResponse($refreshToken);
68 |
69 | return new Token(
70 | Arr::get($response, 'access_token'),
71 | Arr::get($response, 'refresh_token', $refreshToken),
72 | Arr::get($response, 'expires_in'),
73 | explode($this->scopeSeparator, Arr::get($response, 'scope', ''))
74 | );
75 | }
76 |
77 | /**
78 | * {@inheritdoc}
79 | */
80 | protected function mapUserToObject(array $user)
81 | {
82 | // Deprecated: Fields added to keep backwards compatibility in 4.0. These will be removed in 5.0
83 | $user['id'] = Arr::get($user, 'sub');
84 | $user['verified_email'] = Arr::get($user, 'email_verified');
85 | $user['link'] = Arr::get($user, 'profile');
86 |
87 | return (new User)->setRaw($user)->map([
88 | 'id' => Arr::get($user, 'sub'),
89 | 'nickname' => Arr::get($user, 'nickname'),
90 | 'name' => Arr::get($user, 'name'),
91 | 'email' => Arr::get($user, 'email'),
92 | 'avatar' => $avatarUrl = Arr::get($user, 'picture'),
93 | 'avatar_original' => $avatarUrl,
94 | ]);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Two/InvalidStateException.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | protected function getTokenUrl()
35 | {
36 | return 'https://www.linkedin.com/oauth/v2/accessToken';
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | protected function getUserByToken($token)
43 | {
44 | return $this->getBasicProfile($token);
45 | }
46 |
47 | /**
48 | * Get the basic profile fields for the user.
49 | *
50 | * @param string $token
51 | * @return array
52 | */
53 | protected function getBasicProfile($token)
54 | {
55 | $response = $this->getHttpClient()->get('https://api.linkedin.com/v2/userinfo', [
56 | RequestOptions::HEADERS => [
57 | 'Authorization' => 'Bearer '.$token,
58 | 'X-RestLi-Protocol-Version' => '2.0.0',
59 | ],
60 | RequestOptions::QUERY => [
61 | 'projection' => '(sub,email,email_verified,name,given_name,family_name,picture)',
62 | ],
63 | ]);
64 |
65 | return (array) json_decode($response->getBody(), true);
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | */
71 | protected function mapUserToObject(array $user)
72 | {
73 | return (new User)->setRaw($user)->map([
74 | 'id' => $user['sub'],
75 | 'nickname' => null,
76 | 'name' => $user['name'],
77 | 'first_name' => $user['given_name'],
78 | 'last_name' => $user['family_name'],
79 | 'email' => $user['email'] ?? null,
80 | 'email_verified' => $user['email_verified'] ?? null,
81 | 'avatar' => $user['picture'] ?? null,
82 | 'avatar_original' => $user['picture'] ?? null,
83 | ]);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Two/LinkedInProvider.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state);
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | protected function getTokenUrl()
36 | {
37 | return 'https://www.linkedin.com/oauth/v2/accessToken';
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | protected function getUserByToken($token)
44 | {
45 | $basicProfile = $this->getBasicProfile($token);
46 | $emailAddress = $this->getEmailAddress($token);
47 |
48 | return array_merge($basicProfile, $emailAddress);
49 | }
50 |
51 | /**
52 | * Get the basic profile fields for the user.
53 | *
54 | * @param string $token
55 | * @return array
56 | */
57 | protected function getBasicProfile($token)
58 | {
59 | $fields = ['id', 'firstName', 'lastName', 'profilePicture(displayImage~:playableStreams)'];
60 |
61 | if (in_array('r_liteprofile', $this->getScopes())) {
62 | array_push($fields, 'vanityName');
63 | }
64 |
65 | $response = $this->getHttpClient()->get('https://api.linkedin.com/v2/me', [
66 | RequestOptions::HEADERS => [
67 | 'Authorization' => 'Bearer '.$token,
68 | 'X-RestLi-Protocol-Version' => '2.0.0',
69 | ],
70 | RequestOptions::QUERY => [
71 | 'projection' => '('.implode(',', $fields).')',
72 | ],
73 | ]);
74 |
75 | return (array) json_decode($response->getBody(), true);
76 | }
77 |
78 | /**
79 | * Get the email address for the user.
80 | *
81 | * @param string $token
82 | * @return array
83 | */
84 | protected function getEmailAddress($token)
85 | {
86 | $response = $this->getHttpClient()->get('https://api.linkedin.com/v2/emailAddress', [
87 | RequestOptions::HEADERS => [
88 | 'Authorization' => 'Bearer '.$token,
89 | 'X-RestLi-Protocol-Version' => '2.0.0',
90 | ],
91 | RequestOptions::QUERY => [
92 | 'q' => 'members',
93 | 'projection' => '(elements*(handle~))',
94 | ],
95 | ]);
96 |
97 | return (array) Arr::get((array) json_decode($response->getBody(), true), 'elements.0.handle~');
98 | }
99 |
100 | /**
101 | * {@inheritdoc}
102 | */
103 | protected function mapUserToObject(array $user)
104 | {
105 | $preferredLocale = Arr::get($user, 'firstName.preferredLocale.language').'_'.Arr::get($user, 'firstName.preferredLocale.country');
106 | $firstName = Arr::get($user, 'firstName.localized.'.$preferredLocale);
107 | $lastName = Arr::get($user, 'lastName.localized.'.$preferredLocale);
108 |
109 | $images = (array) Arr::get($user, 'profilePicture.displayImage~.elements', []);
110 | $avatar = Arr::first($images, function ($image) {
111 | return (
112 | $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] ??
113 | $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['displaySize']['width']
114 | ) === 100;
115 | });
116 | $originalAvatar = Arr::first($images, function ($image) {
117 | return (
118 | $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] ??
119 | $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['displaySize']['width']
120 | ) === 800;
121 | });
122 |
123 | return (new User)->setRaw($user)->map([
124 | 'id' => $user['id'],
125 | 'nickname' => null,
126 | 'name' => $firstName.' '.$lastName,
127 | 'first_name' => $firstName,
128 | 'last_name' => $lastName,
129 | 'email' => Arr::get($user, 'emailAddress'),
130 | 'avatar' => Arr::get($avatar, 'identifiers.0.identifier'),
131 | 'avatar_original' => Arr::get($originalAvatar, 'identifiers.0.identifier'),
132 | ]);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Two/ProviderInterface.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://slack.com/openid/connect/authorize', $state);
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | protected function getTokenUrl()
36 | {
37 | return 'https://slack.com/api/openid.connect.token';
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | protected function getUserByToken($token)
44 | {
45 | $response = $this->getHttpClient()->get('https://slack.com/api/openid.connect.userInfo', [
46 | RequestOptions::HEADERS => ['Authorization' => 'Bearer '.$token],
47 | ]);
48 |
49 | return json_decode($response->getBody(), true);
50 | }
51 |
52 | /**
53 | * {@inheritdoc}
54 | */
55 | protected function mapUserToObject(array $user)
56 | {
57 | return (new User)->setRaw($user)->map([
58 | 'id' => Arr::get($user, 'sub'),
59 | 'nickname' => null,
60 | 'name' => Arr::get($user, 'name'),
61 | 'email' => Arr::get($user, 'email'),
62 | 'avatar' => Arr::get($user, 'picture'),
63 | 'organization_id' => Arr::get($user, 'https://slack.com/team_id'),
64 | ]);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Two/SlackProvider.php:
--------------------------------------------------------------------------------
1 | scopeKey = 'scope';
32 |
33 | return $this;
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function getAuthUrl($state)
40 | {
41 | return $this->buildAuthUrlFromBase('https://slack.com/oauth/v2/authorize', $state);
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | protected function getTokenUrl()
48 | {
49 | return 'https://slack.com/api/oauth.v2.access';
50 | }
51 |
52 | /**
53 | * {@inheritdoc}
54 | */
55 | protected function getUserByToken($token)
56 | {
57 | $response = $this->getHttpClient()->get('https://slack.com/api/users.identity', [
58 | RequestOptions::HEADERS => ['Authorization' => 'Bearer '.$token],
59 | ]);
60 |
61 | return json_decode($response->getBody(), true);
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | protected function mapUserToObject(array $user)
68 | {
69 | return (new User)->setRaw($user)->map([
70 | 'id' => Arr::get($user, 'user.id'),
71 | 'name' => Arr::get($user, 'user.name'),
72 | 'email' => Arr::get($user, 'user.email'),
73 | 'avatar' => Arr::get($user, 'user.image_512'),
74 | 'organization_id' => Arr::get($user, 'team.id'),
75 | ]);
76 | }
77 |
78 | /**
79 | * {@inheritdoc}
80 | */
81 | protected function getCodeFields($state = null)
82 | {
83 | $fields = parent::getCodeFields($state);
84 |
85 | if ($this->scopeKey === 'user_scope') {
86 | $fields['scope'] = '';
87 | $fields['user_scope'] = $this->formatScopes($this->scopes, $this->scopeSeparator);
88 | }
89 |
90 | return $fields;
91 | }
92 |
93 | /**
94 | * {@inheritdoc}
95 | */
96 | public function getAccessTokenResponse($code)
97 | {
98 | $response = $this->getHttpClient()->post($this->getTokenUrl(), [
99 | RequestOptions::HEADERS => $this->getTokenHeaders($code),
100 | RequestOptions::FORM_PARAMS => $this->getTokenFields($code),
101 | ]);
102 |
103 | $result = json_decode($response->getBody(), true);
104 |
105 | if ($this->scopeKey === 'user_scope') {
106 | return $result['authed_user'];
107 | }
108 |
109 | return $result;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Two/Token.php:
--------------------------------------------------------------------------------
1 | token = $token;
46 | $this->refreshToken = $refreshToken;
47 | $this->expiresIn = $expiresIn;
48 | $this->approvedScopes = $approvedScopes;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Two/TwitchProvider.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://id.twitch.tv/oauth2/authorize', $state);
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | protected function getTokenUrl()
36 | {
37 | return 'https://id.twitch.tv/oauth2/token';
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | protected function getUserByToken($token)
44 | {
45 | $response = $this->getHttpClient()->get(
46 | 'https://api.twitch.tv/helix/users',
47 | [
48 | RequestOptions::HEADERS => [
49 | 'Accept' => 'application/json',
50 | 'Authorization' => 'Bearer '.$token,
51 | 'Client-ID' => $this->clientId,
52 | ],
53 | ]
54 | );
55 |
56 | return json_decode((string) $response->getBody(), true);
57 | }
58 |
59 | /**
60 | * Create a user instance from the given data.
61 | *
62 | * @param array $response
63 | * @param array $user
64 | * @return \Laravel\Socialite\Two\User
65 | */
66 | protected function userInstance(array $response, array $user)
67 | {
68 | $this->user = $this->mapUserToObject($user);
69 |
70 | $scopes = Arr::get($response, 'scope', []);
71 |
72 | if (! is_array($scopes)) {
73 | $scopes = explode($this->scopeSeparator, $scopes);
74 | }
75 |
76 | return $this->user->setToken(Arr::get($response, 'access_token'))
77 | ->setRefreshToken(Arr::get($response, 'refresh_token'))
78 | ->setExpiresIn(Arr::get($response, 'expires_in'))
79 | ->setApprovedScopes($scopes);
80 | }
81 |
82 | /**
83 | * {@inheritdoc}
84 | */
85 | protected function mapUserToObject(array $user)
86 | {
87 | $user = $user['data']['0'];
88 |
89 | return (new User)->setRaw($user)->map([
90 | 'id' => $user['id'],
91 | 'nickname' => $user['display_name'],
92 | 'name' => $user['display_name'],
93 | 'email' => Arr::get($user, 'email'),
94 | 'avatar' => $user['profile_image_url'],
95 | ]);
96 | }
97 |
98 | /**
99 | * {@inheritdoc}
100 | */
101 | public function refreshToken($refreshToken)
102 | {
103 | $response = $this->getRefreshTokenResponse($refreshToken);
104 |
105 | return new Token(
106 | Arr::get($response, 'access_token'),
107 | Arr::get($response, 'refresh_token'),
108 | Arr::get($response, 'expires_in'),
109 | Arr::get($response, 'scope', [])
110 | );
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Two/TwitterProvider.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://twitter.com/i/oauth2/authorize', $state);
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | protected function getTokenUrl()
50 | {
51 | return 'https://api.twitter.com/2/oauth2/token';
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | protected function getUserByToken($token)
58 | {
59 | $response = $this->getHttpClient()->get('https://api.twitter.com/2/users/me', [
60 | RequestOptions::HEADERS => ['Authorization' => 'Bearer '.$token],
61 | RequestOptions::QUERY => ['user.fields' => 'profile_image_url,confirmed_email'],
62 | ]);
63 |
64 | return Arr::get(json_decode($response->getBody(), true), 'data');
65 | }
66 |
67 | /**
68 | * {@inheritdoc}
69 | */
70 | protected function mapUserToObject(array $user)
71 | {
72 | return (new User)->setRaw($user)->map([
73 | 'id' => $user['id'],
74 | 'email' => $user['confirmed_email'] ?? null,
75 | 'nickname' => $user['username'],
76 | 'name' => $user['name'],
77 | 'avatar' => $user['profile_image_url'],
78 | ]);
79 | }
80 |
81 | /**
82 | * {@inheritdoc}
83 | */
84 | public function getAccessTokenResponse($code)
85 | {
86 | $response = $this->getHttpClient()->post($this->getTokenUrl(), [
87 | RequestOptions::HEADERS => ['Accept' => 'application/json'],
88 | RequestOptions::AUTH => [$this->clientId, $this->clientSecret],
89 | RequestOptions::FORM_PARAMS => $this->getTokenFields($code),
90 | ]);
91 |
92 | return json_decode($response->getBody(), true);
93 | }
94 |
95 | /**
96 | * {@inheritdoc}
97 | */
98 | protected function getRefreshTokenResponse($refreshToken)
99 | {
100 | $response = $this->getHttpClient()->post($this->getTokenUrl(), [
101 | RequestOptions::HEADERS => ['Accept' => 'application/json'],
102 | RequestOptions::AUTH => [$this->clientId, $this->clientSecret],
103 | RequestOptions::FORM_PARAMS => [
104 | 'grant_type' => 'refresh_token',
105 | 'refresh_token' => $refreshToken,
106 | 'client_id' => $this->clientId,
107 | ],
108 | ]);
109 |
110 | return json_decode($response->getBody(), true);
111 | }
112 |
113 | /**
114 | * {@inheritdoc}
115 | */
116 | protected function getCodeFields($state = null)
117 | {
118 | $fields = parent::getCodeFields($state);
119 |
120 | if ($this->isStateless()) {
121 | $fields['state'] = 'state';
122 | }
123 |
124 | return $fields;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Two/User.php:
--------------------------------------------------------------------------------
1 | token = $token;
46 |
47 | return $this;
48 | }
49 |
50 | /**
51 | * Set the refresh token required to obtain a new access token.
52 | *
53 | * @param string $refreshToken
54 | * @return $this
55 | */
56 | public function setRefreshToken($refreshToken)
57 | {
58 | $this->refreshToken = $refreshToken;
59 |
60 | return $this;
61 | }
62 |
63 | /**
64 | * Set the number of seconds the access token is valid for.
65 | *
66 | * @param int $expiresIn
67 | * @return $this
68 | */
69 | public function setExpiresIn($expiresIn)
70 | {
71 | $this->expiresIn = $expiresIn;
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * Set the scopes that were approved by the user during authentication.
78 | *
79 | * @param array $approvedScopes
80 | * @return $this
81 | */
82 | public function setApprovedScopes($approvedScopes)
83 | {
84 | $this->approvedScopes = $approvedScopes;
85 |
86 | return $this;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Two/XProvider.php:
--------------------------------------------------------------------------------
1 | buildAuthUrlFromBase('https://x.com/i/oauth2/authorize', $state);
16 | }
17 |
18 | /**
19 | * {@inheritdoc}
20 | */
21 | protected function getTokenUrl()
22 | {
23 | return 'https://api.x.com/2/oauth2/token';
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | protected function getUserByToken($token)
30 | {
31 | $response = $this->getHttpClient()->get('https://api.x.com/2/users/me', [
32 | RequestOptions::HEADERS => ['Authorization' => 'Bearer '.$token],
33 | RequestOptions::QUERY => ['user.fields' => 'profile_image_url,confirmed_email'],
34 | ]);
35 |
36 | return Arr::get(json_decode($response->getBody(), true), 'data');
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | protected function mapUserToObject(array $user)
43 | {
44 | $user = parent::mapUserToObject($user);
45 |
46 | $user->email = $user['confirmed_email'];
47 |
48 | return $user;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------