Completed
Push — develop ( 8c7de8...b64521 )
by Wisoot
02:54
created

src/JwtGuard.php (2 issues)

Upgrade to new PHP Analysis Engine

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
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...
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
}