These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace WWON\JwtGuard; |
||
4 | |||
5 | use Illuminate\Auth\Events\Attempting; |
||
6 | use Illuminate\Auth\Events\Login; |
||
7 | use Illuminate\Auth\Events\Logout; |
||
8 | use Illuminate\Auth\GuardHelpers; |
||
9 | use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; |
||
10 | use Illuminate\Contracts\Auth\Guard; |
||
11 | use Illuminate\Contracts\Auth\UserProvider; |
||
12 | use Illuminate\Http\Request; |
||
13 | use Illuminate\Support\Facades\Config; |
||
14 | use WWON\JwtGuard\Exceptions\Exception; |
||
15 | use WWON\JwtGuard\Exceptions\InaccessibleException; |
||
16 | use WWON\JwtGuard\Exceptions\InvalidTokenException; |
||
17 | use WWON\JwtGuard\Exceptions\MalformedException; |
||
18 | use WWON\JwtGuard\Exceptions\TokenExpiredException; |
||
19 | |||
20 | class JwtGuard implements Guard |
||
21 | { |
||
22 | |||
23 | use GuardHelpers; |
||
24 | |||
25 | /** |
||
26 | * @var string |
||
27 | */ |
||
28 | protected $token; |
||
29 | |||
30 | /** |
||
31 | * @var bool |
||
32 | */ |
||
33 | protected $isTokenRefreshable = false; |
||
34 | |||
35 | /** |
||
36 | * @var JwtService |
||
37 | */ |
||
38 | protected $jwtService; |
||
39 | |||
40 | /** |
||
41 | * @var Request |
||
42 | */ |
||
43 | protected $request; |
||
44 | |||
45 | /** |
||
46 | * Indicates if the logout method has been called. |
||
47 | * |
||
48 | * @var bool |
||
49 | */ |
||
50 | protected $loggedOut = false; |
||
51 | |||
52 | /** |
||
53 | * JwtGuard constructor |
||
54 | * |
||
55 | * @param UserProvider $provider |
||
56 | * @param JwtService $jwtService |
||
57 | * @param Request|null $request |
||
58 | */ |
||
59 | public function __construct( |
||
60 | UserProvider $provider, |
||
61 | JwtService $jwtService, |
||
62 | Request $request = null |
||
63 | ) { |
||
64 | $this->provider = $provider; |
||
65 | $this->jwtService = $jwtService; |
||
66 | $this->request = $request; |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * Get the currently authenticated user. |
||
71 | * |
||
72 | * @return \Illuminate\Contracts\Auth\Authenticatable|null |
||
73 | */ |
||
74 | public function user() |
||
75 | { |
||
76 | // If we've already retrieved the user for the current request we can just |
||
77 | // return it back immediately. We do not want to fetch the user data on |
||
78 | // every call to this method because that would be tremendously slow. |
||
79 | if ($this->user) { |
||
80 | return $this->user; |
||
81 | } |
||
82 | |||
83 | if (!$token = $this->getBearerToken()) { |
||
84 | return $this->user = null; |
||
85 | } |
||
86 | |||
87 | try { |
||
88 | $this->user = $this->getUserByToken($token); |
||
89 | } catch (InaccessibleException $e) { |
||
90 | $this->isTokenRefreshable = true; |
||
91 | $this->user = null; |
||
92 | } catch (Exception $e) { |
||
93 | $this->user = null; |
||
94 | } |
||
95 | |||
96 | return $this->user; |
||
97 | } |
||
98 | |||
99 | /** |
||
100 | * Retrieve the user by the given payload. |
||
101 | * |
||
102 | * @param string $token |
||
103 | * @return AuthenticatableContract|null |
||
104 | * @throws InaccessibleException |
||
105 | * @throws MalformedException |
||
106 | * @throws TokenExpiredException |
||
107 | * @throws InvalidTokenException |
||
108 | */ |
||
109 | protected function getUserByToken($token) |
||
110 | { |
||
111 | $claim = $this->jwtService->getClaimFromToken($token); |
||
112 | $user = $this->provider->retrieveById($claim->sub); |
||
113 | |||
114 | if (!empty($user) && get_class($user) !== $claim->aud) { |
||
115 | throw new InvalidTokenException; |
||
116 | } |
||
117 | |||
118 | return $user; |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * Validate a user's credentials. |
||
123 | * |
||
124 | * @param array $credentials |
||
125 | * @return bool |
||
126 | */ |
||
127 | public function validate(array $credentials = []) |
||
128 | { |
||
129 | return $this->attempt($credentials, false); |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Attempt to authenticate a user using the given credentials. |
||
134 | * |
||
135 | * @param array $credentials |
||
136 | * @param bool $login |
||
137 | * @return bool |
||
138 | */ |
||
139 | public function attempt(array $credentials = [], $login = true) |
||
140 | { |
||
141 | $this->fireAttemptEvent($credentials, $login); |
||
142 | |||
143 | $user = $this->provider->retrieveByCredentials($credentials); |
||
144 | |||
145 | // If an implementation of UserInterface was returned, we'll ask the provider |
||
146 | // to validate the user against the given credentials, and if they are in |
||
147 | // fact valid we'll log the users into the application and return true. |
||
148 | if ($this->hasValidCredentials($user, $credentials)) { |
||
149 | if ($login) { |
||
150 | $this->login($user); |
||
151 | } |
||
152 | |||
153 | return true; |
||
154 | } |
||
155 | |||
156 | return false; |
||
157 | } |
||
158 | |||
159 | /** |
||
160 | * Determine if the user matches the credentials. |
||
161 | * |
||
162 | * @param mixed $user |
||
163 | * @param array $credentials |
||
164 | * @return bool |
||
165 | */ |
||
166 | protected function hasValidCredentials($user, $credentials) |
||
167 | { |
||
168 | return ! is_null($user) && $this->provider->validateCredentials($user, $credentials); |
||
169 | } |
||
170 | |||
171 | /** |
||
172 | * Fire the attempt event with the arguments. |
||
173 | * |
||
174 | * @param array $credentials |
||
175 | * @param bool $login |
||
176 | * @return void |
||
177 | */ |
||
178 | protected function fireAttemptEvent(array $credentials, $login) |
||
179 | { |
||
180 | if (isset($this->events)) { |
||
181 | $this->events->fire(new Attempting( |
||
182 | $credentials, false, $login |
||
183 | )); |
||
184 | } |
||
185 | } |
||
186 | |||
187 | /** |
||
188 | * Register an authentication attempt event listener. |
||
189 | * |
||
190 | * @param mixed $callback |
||
191 | * @return void |
||
192 | */ |
||
193 | public function attempting($callback) |
||
194 | { |
||
195 | if (isset($this->events)) { |
||
196 | $this->events->listen(Attempting::class, $callback); |
||
197 | } |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Log a user into the application. |
||
202 | * |
||
203 | * @param \Illuminate\Contracts\Auth\Authenticatable $user |
||
204 | * @return void |
||
205 | */ |
||
206 | public function login(AuthenticatableContract $user) |
||
207 | { |
||
208 | $claim = new Claim([ |
||
209 | 'sub' => $user->getAuthIdentifier(), |
||
210 | 'aud' => get_class($user), |
||
211 | 'refresh' => Config::get('jwt.refreshable') |
||
212 | ]); |
||
213 | |||
214 | $token = $this->jwtService->getTokenForClaim($claim); |
||
215 | |||
216 | // If we have an event dispatcher instance set we will fire an event so that |
||
217 | // any listeners will hook into the authentication events and run actions |
||
218 | // based on the login and logout events fired from the guard instances. |
||
219 | $this->fireLoginEvent($user); |
||
220 | |||
221 | $this->setToken($token); |
||
222 | $this->setUser($user); |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * generateTokenForUser method |
||
227 | * |
||
228 | * @param string $token |
||
229 | * @return string |
||
230 | */ |
||
231 | protected function refreshTokenForUser($token) |
||
232 | { |
||
233 | try { |
||
234 | $newToken = $this->jwtService->refreshToken($token); |
||
235 | } catch (Exception $e) { |
||
236 | $newToken = null; |
||
237 | } |
||
238 | |||
239 | return $newToken; |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * Fire the login event if the dispatcher is set. |
||
244 | * |
||
245 | * @param \Illuminate\Contracts\Auth\Authenticatable $user |
||
246 | * @param bool $remember |
||
247 | * @return void |
||
248 | */ |
||
249 | protected function fireLoginEvent($user, $remember = false) |
||
250 | { |
||
251 | if (isset($this->events)) { |
||
252 | $this->events->fire(new Login($user, $remember)); |
||
253 | } |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * Log the given user ID into the application. |
||
258 | * |
||
259 | * @param mixed $id |
||
260 | * @return \Illuminate\Contracts\Auth\Authenticatable |
||
261 | */ |
||
262 | public function loginUsingId($id) |
||
263 | { |
||
264 | $this->login($user = $this->provider->retrieveById($id)); |
||
265 | |||
266 | return $user; |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Log the user out of the application. |
||
271 | * |
||
272 | * @return void |
||
273 | */ |
||
274 | View Code Duplication | public function logout() |
|
0 ignored issues
–
show
|
|||
275 | { |
||
276 | if (!$token = $this->getBearerToken()) { |
||
277 | return; |
||
278 | } |
||
279 | |||
280 | try { |
||
281 | $this->jwtService->invalidateToken($token); |
||
282 | } catch (Exception $e) { } |
||
283 | |||
284 | if (isset($this->events)) { |
||
285 | $this->events->fire(new Logout($this->user)); |
||
286 | } |
||
287 | |||
288 | // Once we have fired the logout event we will clear the users out of memory |
||
289 | // so they are no longer available as the user is no longer considered as |
||
290 | // being signed into this application and should not be available here. |
||
291 | $this->user = null; |
||
292 | $this->token = null; |
||
293 | $this->loggedOut = true; |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * log this user out from every token |
||
298 | * |
||
299 | * @return void |
||
300 | */ |
||
301 | View Code Duplication | public function logoutAll() |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
302 | { |
||
303 | if (!$token = $this->getBearerToken()) { |
||
304 | return; |
||
305 | } |
||
306 | |||
307 | try { |
||
308 | $user = $this->jwtService->getClaimFromToken($token); |
||
309 | |||
310 | $this->jwtService->wipeUserTokens($user); |
||
311 | |||
312 | } catch (Exception $e) { } |
||
313 | |||
314 | if (isset($this->events)) { |
||
315 | $this->events->fire(new Logout($this->user)); |
||
316 | } |
||
317 | |||
318 | // Once we have fired the logout event we will clear the users out of memory |
||
319 | // so they are no longer available as the user is no longer considered as |
||
320 | // being signed into this application and should not be available here. |
||
321 | $this->user = null; |
||
322 | $this->token = null; |
||
323 | $this->loggedOut = true; |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * Refresh user token |
||
328 | * |
||
329 | * @return string|null |
||
330 | */ |
||
331 | public function refreshToken() |
||
332 | { |
||
333 | if (!$token = $this->getBearerToken()) { |
||
334 | return null; |
||
335 | } |
||
336 | |||
337 | $this->token = $this->refreshTokenForUser($token); |
||
338 | |||
339 | return $this->token; |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * setToken method |
||
344 | * |
||
345 | * @param string $token |
||
346 | */ |
||
347 | public function setToken($token) |
||
348 | { |
||
349 | $this->token = $token; |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * getToken method |
||
354 | * |
||
355 | * @return null|string |
||
356 | */ |
||
357 | public function getToken() |
||
358 | { |
||
359 | return $this->token; |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * isTokenRefreshable method |
||
364 | */ |
||
365 | public function isTokenRefreshable() |
||
366 | { |
||
367 | return $this->isTokenRefreshable; |
||
368 | } |
||
369 | |||
370 | /** |
||
371 | * getBearerToken method |
||
372 | * |
||
373 | * @return string|null |
||
374 | */ |
||
375 | protected function getBearerToken() |
||
376 | { |
||
377 | $header = $this->request->header('Authorization', ''); |
||
378 | |||
379 | if (starts_with(strtolower($header), 'bearer ')) { |
||
380 | return mb_substr($header, 7, null, 'UTF-8'); |
||
381 | } |
||
382 | |||
383 | return null; |
||
384 | } |
||
385 | |||
386 | } |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.