JWTAuthenticator::createToken()   B
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 8.439
c 0
b 0
f 0
cc 5
eloc 14
nc 5
nop 2
1
<?php
2
3
/**
4
 * This file is part of tenside/core-bundle.
5
 *
6
 * (c) Christian Schiffler <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core-bundle
14
 * @author     Christian Schiffler <[email protected]>
15
 * @copyright  2015 Christian Schiffler <[email protected]>
16
 * @license    https://github.com/tenside/core-bundle/blob/master/LICENSE MIT
17
 * @link       https://github.com/tenside/core-bundle
18
 * @filesource
19
 */
20
21
namespace Tenside\CoreBundle\Security;
22
23
use Symfony\Component\HttpFoundation\Request;
24
use Symfony\Component\HttpFoundation\Response;
25
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
26
use Symfony\Component\Security\Core\Exception\AuthenticationException;
27
use Symfony\Component\Security\Core\User\UserProviderInterface;
28
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
29
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
30
use Tenside\Core\Config\TensideJsonConfig;
31
32
/**
33
 * This class validates jwt.
34
 */
35
class JWTAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
36
{
37
    /**
38
     * The client secret used for encoding and decoding.
39
     *
40
     * @var string
41
     */
42
    private $secret;
43
44
    /**
45
     * The local id.
46
     *
47
     * @var string
48
     */
49
    private $localId;
50
51
    /**
52
     * Create a new instance.
53
     *
54
     * @param TensideJsonConfig $config The configuration.
55
     *
56
     * @throws \LogicException When no secret has been defined.
57
     */
58
    public function __construct(TensideJsonConfig $config)
59
    {
60
        $this->secret  = $config->getSecret();
61
        $this->localId = $config->getLocalDomain();
62
    }
63
64
    /**
65
     * Create a token from the passed user information.
66
     *
67
     * @param UserInformationInterface $userData The user data to issue a token for.
68
     *
69
     * @param int|null                 $lifetime The lifetime in seconds this token shall be valid.
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $lifetime a bit more specific; maybe use integer.
Loading history...
70
     *                                           Use null for no limit.
71
     *
72
     * @return string
73
     */
74
    public function getTokenForData(UserInformationInterface $userData, $lifetime = 3600)
75
    {
76
        return $this->encode($lifetime, $userData->values());
77
    }
78
79
    /**
80
     * Create the token.
81
     *
82
     * @param Request $request     The request being processed.
83
     *
84
     * @param string  $providerKey The provider key.
85
     *
86
     * @return JavascriptWebToken|null
87
     */
88
    public function createToken(Request $request, $providerKey)
89
    {
90
        if (!$this->secret) {
91
            return null;
92
        }
93
94
        // look for an authorization header
95
        $authorizationHeader = $request->headers->get('Authorization');
96
97
        if ($authorizationHeader === null) {
98
            return null;
99
        }
100
101
        if (0 !== stripos($authorizationHeader, 'Bearer ')) {
102
            return null;
103
        }
104
105
        // extract the JWT
106
        $authToken = substr($authorizationHeader, 7);
107
108
        try {
109
            // decode and validate the JWT - will throw exceptions for various conditions.
110
            $token = $this->decodeToken($authToken);
111
        } catch (\Exception $exception) {
112
            return null;
113
        }
114
115
        return new JavascriptWebToken($token, $providerKey);
0 ignored issues
show
Documentation introduced by
$token is of type object, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
116
    }
117
118
    /**
119
     * Authenticate the passed token.
120
     *
121
     * @param TokenInterface        $token        The token to authenticate.
122
     *
123
     * @param UserProviderInterface $userProvider The user provider.
124
     *
125
     * @param string                $providerKey  The provider key.
126
     *
127
     * @return JavascriptWebToken
128
     *
129
     * @throws \LogicException When no secret is in the config and therefore the token can not be authenticated.
130
     *
131
     * @throws AuthenticationException When the token is invalid.
132
     */
133
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
134
    {
135
        if (!$this->secret) {
136
            throw new \LogicException('Config does not contain a secret.');
137
        }
138
139
        if ((null === ($credentials = $token->getCredentials())) || !is_object($credentials)) {
140
            throw new AuthenticationException(sprintf('Invalid token - no or invalid credentials.'));
141
        }
142
143
        // Get the user for the injected UserProvider
144
        $user = $userProvider->loadUserByUsername($credentials->username);
145
146
        if (!$user) {
147
            throw new AuthenticationException(sprintf('Invalid token - could not derive user from credentials.'));
148
        }
149
150
        return new JavascriptWebToken($credentials, $providerKey, $user, $user->getRoles());
0 ignored issues
show
Documentation introduced by
$credentials is of type object, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
151
    }
152
153
    /**
154
     * Test if we support the token.
155
     *
156
     * @param TokenInterface $token       The token to test.
157
     *
158
     * @param string         $providerKey The provider key.
159
     *
160
     * @return bool
161
     */
162
    public function supportsToken(TokenInterface $token, $providerKey)
163
    {
164
        return ($token instanceof JavascriptWebToken) && ($token->getProviderKey() === $providerKey);
165
    }
166
167
    /**
168
     * Generate a proper "unauthorized" response.
169
     *
170
     * @param Request                 $request   The request to generate the response for.
171
     *
172
     * @param AuthenticationException $exception The exception to generate the response for.
173
     *
174
     * @return Response
175
     *
176
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
177
     */
178
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
179
    {
180
        return new Response('Authentication Failed: ' . $exception->getMessage(), Response::HTTP_UNAUTHORIZED);
181
    }
182
183
    /**
184
     * Decode a token.
185
     *
186
     * @param string $jwt The jwt as string.
187
     *
188
     * @return object
189
     *
190
     * @throws \UnexpectedValueException When the token does not match the local id.
191
     */
192
    private function decodeToken($jwt)
193
    {
194
        // Decode the token.
195
        $decodedToken = \JWT::decode($jwt, $this->secret, ['HS256']);
196
197
        // Validate that this JWT was made for us.
198
        $aud = property_exists($decodedToken, 'aud') ? $decodedToken->aud : null;
199
        if ($aud !== $this->localId) {
200
            throw new \UnexpectedValueException('This token is not intended for us.');
201
        }
202
203
        return $decodedToken;
204
    }
205
206
    /**
207
     * Encode a token.
208
     *
209
     * @param int|null   $lifetime      The lifetime in seconds this token shall be valid. Use null for no limit.
210
     *
211
     * @param null|array $customPayload Any custom payload to be added to the token.
212
     *
213
     * @return string
214
     */
215
    private function encode($lifetime, $customPayload = null)
216
    {
217
        $time    = time();
218
        $payload = ['iat' => $time];
219
220
        if (null !== $customPayload) {
221
            $payload = array_merge($customPayload, $payload);
222
        }
223
224
        $jti = md5(json_encode($payload));
225
226
        $payload['jti'] = $jti;
227
228
        if (null !== $this->localId) {
229
            $payload['aud'] = $this->localId;
230
        }
231
232
        if (null !== $lifetime) {
233
            $payload['exp'] = ($time + $lifetime);
234
        }
235
236
        $jwt = \JWT::encode($payload, $this->secret);
237
238
        return $jwt;
239
    }
240
}
241