1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Firesphere\GraphQLJWT; |
4
|
|
|
|
5
|
|
|
use BadMethodCallException; |
6
|
|
|
use Lcobucci\JWT\Builder; |
7
|
|
|
use Lcobucci\JWT\Parser; |
8
|
|
|
use Lcobucci\JWT\Signer\Hmac\Sha256; |
9
|
|
|
use Lcobucci\JWT\Token; |
10
|
|
|
use SilverStripe\Control\Director; |
11
|
|
|
use SilverStripe\Control\HTTPRequest; |
12
|
|
|
use SilverStripe\Core\Config\Configurable; |
13
|
|
|
use SilverStripe\GraphQL\Controller; |
14
|
|
|
use SilverStripe\ORM\ValidationException; |
15
|
|
|
use SilverStripe\ORM\ValidationResult; |
16
|
|
|
use SilverStripe\Security\Authenticator; |
17
|
|
|
use SilverStripe\Security\Member; |
18
|
|
|
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator; |
19
|
|
|
|
20
|
|
|
class JWTAuthenticator extends MemberAuthenticator |
21
|
|
|
{ |
22
|
|
|
use Configurable; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* JWT is stateless, therefore, we don't support anything but login |
26
|
|
|
* |
27
|
|
|
* @return int |
28
|
|
|
*/ |
29
|
|
|
public function supportedServices() |
30
|
|
|
{ |
31
|
|
|
return Authenticator::LOGIN | Authenticator::CMS_LOGIN; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @param array $data |
36
|
|
|
* @param HTTPRequest $request |
37
|
|
|
* @param ValidationResult|null $result |
38
|
|
|
* @return Member|null |
39
|
|
|
* @throws \OutOfBoundsException |
40
|
|
|
* @throws \BadMethodCallException |
41
|
|
|
*/ |
42
|
|
|
public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null) |
43
|
|
|
{ |
44
|
|
|
if (!$result) { |
45
|
|
|
$result = new ValidationResult(); |
46
|
|
|
} |
47
|
|
|
$token = $data['token']; |
48
|
|
|
|
49
|
|
|
return $this->validateToken($token, $result); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @param Member $member |
54
|
|
|
* @return Token |
55
|
|
|
* @throws ValidationException |
56
|
|
|
* @throws BadMethodCallException |
57
|
|
|
*/ |
58
|
|
|
public function generateToken(Member $member) |
59
|
|
|
{ |
60
|
|
|
$config = static::config(); |
61
|
|
|
$signer = new Sha256(); |
62
|
|
|
$uniqueID = uniqid(getenv('JWT_PREFIX'), true); |
63
|
|
|
|
64
|
|
|
$request = Controller::curr()->getRequest(); |
65
|
|
|
$audience = $request->getHeader('Origin'); |
66
|
|
|
$signerKey = getenv('JWT_SIGNER_KEY'); |
67
|
|
|
|
68
|
|
|
$builder = new Builder(); |
69
|
|
|
$token = $builder |
|
|
|
|
70
|
|
|
// Configures the issuer (iss claim) |
71
|
|
|
->setIssuer(Director::absoluteBaseURL()) |
|
|
|
|
72
|
|
|
// Configures the audience (aud claim) |
73
|
|
|
->setAudience($audience) |
74
|
|
|
// Configures the id (jti claim), replicating as a header item |
75
|
|
|
->setId($uniqueID, true) |
76
|
|
|
// Configures the time that the token was issue (iat claim) |
77
|
|
|
->setIssuedAt(time()) |
78
|
|
|
// Configures the time that the token can be used (nbf claim) |
79
|
|
|
->setNotBefore(time() + $config->get('nbf_time')) |
80
|
|
|
// Configures the expiration time of the token (nbf claim) |
81
|
|
|
->setExpiration(time() + $config->get('nbf_expiration')) |
82
|
|
|
// Configures a new claim, called "uid" |
83
|
|
|
->set('uid', $member->ID) |
84
|
|
|
// Sign the key with the Signer's key @todo: support certificates |
85
|
|
|
->sign($signer, $signerKey); |
86
|
|
|
|
87
|
|
|
// Save the member if it's not anonymous |
88
|
|
|
if ($member->ID > 0) { |
89
|
|
|
$member->JWTUniqueID = $uniqueID; |
90
|
|
|
$member->write(); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
// Return the token |
94
|
|
|
return $token->getToken(); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @param string $token |
99
|
|
|
* @param ValidationResult $result |
100
|
|
|
* @return null|Member |
101
|
|
|
* @throws \OutOfBoundsException |
102
|
|
|
* @throws \BadMethodCallException |
103
|
|
|
*/ |
104
|
|
|
private function validateToken($token, &$result) |
105
|
|
|
{ |
106
|
|
|
$parser = new Parser(); |
107
|
|
|
$parsedToken = $parser->parse((string)$token); |
108
|
|
|
$signer = new Sha256(); |
109
|
|
|
$signerKey = getenv('JWT_SIGNER_KEY'); |
110
|
|
|
$member = null; |
111
|
|
|
|
112
|
|
|
// If the token is not verified, just give up |
113
|
|
|
if (!$parsedToken->verify($signer, $signerKey)) { |
|
|
|
|
114
|
|
|
$result->addError('Invalid token'); |
115
|
|
|
} |
116
|
|
|
// An expired token can be renewed |
117
|
|
|
elseif ($parsedToken->isExpired()) { |
118
|
|
|
$result->addError('Token is expired, please renew your token with a refreshToken query'); |
119
|
|
|
} |
120
|
|
|
// Everything seems fine, let's find a user |
121
|
|
|
elseif ($parsedToken->getClaim('uid') > 0 && $parsedToken->getClaim('jti')) { |
|
|
|
|
122
|
|
|
/** @var Member $member */ |
123
|
|
|
$member = Member::get() |
124
|
|
|
->filter(['JWTUniqueID' => $parsedToken->getClaim('jti')]) |
|
|
|
|
125
|
|
|
->byID($parsedToken->getClaim('uid')); |
|
|
|
|
126
|
|
|
} |
127
|
|
|
// Not entirely fine, do we allow anonymous users? |
128
|
|
|
// Then, if the token is valid, return an anonymous user |
129
|
|
|
if ( |
130
|
|
|
$result->isValid() && |
131
|
|
|
$parsedToken->getClaim('uid') === 0 && |
|
|
|
|
132
|
|
|
static::config()->get('anonymous_allowed') |
133
|
|
|
) { |
134
|
|
|
$member = Member::create(['ID' => 0, 'FirstName' => 'Anonymous']); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
return $result->isValid() ? $member : null; |
138
|
|
|
|
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.