Failed Conditions
Push — master ( 7c66af...b69c62 )
by Sébastien
02:24
created

TokenManager   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 133
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 53
dl 0
loc 133
ccs 0
cts 87
cp 0
rs 10
c 0
b 0
f 0
wmc 18

9 Methods

Rating   Name   Duplication   Size   Complexity  
A isExpired() 0 3 1
A ensureValidSignature() 0 5 2
A signToken() 0 4 1
A ensureNotExpired() 0 5 2
A parseToken() 0 7 2
A createNewToken() 0 19 3
A getValidatedToken() 0 30 5
A __construct() 0 6 1
A verifySignature() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Service;
6
7
use App\Service\Exception\TokenAudienceException;
8
use App\Service\Exception\TokenExpiredException;
9
use App\Service\Exception\TokenIssuerException;
10
use App\Service\Exception\TokenParseException;
11
use App\Service\Exception\TokenSignatureException;
12
use App\Service\Exception\TokenValidationExceptionInterface;
13
use Lcobucci\JWT\Builder;
14
use Lcobucci\JWT\Parser;
15
use Lcobucci\JWT\Signer\Hmac\Sha256;
16
use Lcobucci\JWT\Token;
17
use Lcobucci\JWT\ValidationData;
18
use Ramsey\Uuid\Uuid;
19
20
class TokenManager
21
{
22
    private $signer;
23
    private $issuer;
24
    private $audience;
25
    private $privateKey;
26
27
    public function __construct(string $privateKey)
28
    {
29
        $this->signer     = new Sha256();
30
        $this->issuer     = $_SERVER['SERVER_NAME'];
31
        $this->audience   = $_SERVER['SERVER_NAME'];
32
        $this->privateKey = $privateKey;
33
    }
34
35
    public function createNewToken(array $customClaims = [], int $expiration = 3600, bool $autoSign = true): Token
36
    {
37
        $builder = (new Builder())
38
            ->setIssuer($this->issuer) // Configures the issuer (iss claim)
39
            ->setAudience($this->audience) // Configures the audience (aud claim)
40
            ->setId(Uuid::uuid1()->toString(), true) // Configures the id (jti claim), replicating as a header item
41
            ->setIssuedAt(time()) // Configures the time that the token was issue (iat claim)
42
            ->setNotBefore(time() + 0) // Configures the time that the token can be used (nbf claim)
43
            ->setExpiration(time() + $expiration); // Configures the expiration time of the token (exp claim)
44
45
        foreach ($customClaims as $key => $value) {
46
            $builder->set($key, $value);
47
        }
48
49
        if ($autoSign) {
50
            return $this->signToken($builder);
51
        }
52
53
        return $builder->getToken();
54
    }
55
56
    /**
57
     * Sign the token.
58
     */
59
    public function signToken(Builder $builder): Token
60
    {
61
        return $builder->sign($this->signer, $this->privateKey)
62
            ->getToken(); // Retrieves the generated token
63
    }
64
65
    /**
66
     * @throws TokenParseException
67
     */
68
    public function parseToken(string $tokenString): Token
69
    {
70
        $tokenParser = new Parser();
71
        try {
72
            return $tokenParser->parse($tokenString);
73
        } catch (\Throwable $e) {
74
            throw new TokenParseException($e->getMessage());
75
        }
76
    }
77
78
    /**
79
     * Ensure that the token signature is valid and
80
     * the token have not been tampered.
81
     *
82
     * @throws TokenSignatureException
83
     */
84
    public function ensureValidSignature(Token $token): void
85
    {
86
        if (!$this->verifySignature($token)) {
87
            throw new TokenSignatureException(sprintf(
88
                'Token failed signature verification.'
89
            ));
90
        }
91
    }
92
93
    /**
94
     * @throw TokenExpiredException
95
     */
96
    public function ensureNotExpired(Token $token): void
97
    {
98
        if ($this->isExpired($token)) {
99
            throw new TokenExpiredException(sprintf(
100
                'Token validity has expired.'
101
            ));
102
        }
103
    }
104
105
    /**
106
     * @throws TokenValidationExceptionInterface the main one
107
     * @throws TokenParseException
108
     * @throws TokenExpiredException
109
     * @throws TokenSignatureException
110
     * @throws TokenIssuerException
111
     * @throws TokenAudienceException
112
     */
113
    public function getValidatedToken(string $tokenString): Token
114
    {
115
        $token = $this->parseToken($tokenString);
116
117
        $this->ensureValidSignature($token);
118
        $this->ensureNotExpired($token);
119
120
        $data = new ValidationData(); // It will use the current time to validate (iat, nbf and exp)
121
        $data->setIssuer($this->issuer);
122
        $data->setAudience($this->audience);
123
124
        if ($token->hasClaim('iss')) {
125
            $issuer = $token->getClaim('iss', false);
126
            if ($issuer !== $this->issuer) {
127
                throw new TokenIssuerException(sprintf(
128
                    'Token issuer does not match'
129
                ));
130
            }
131
        }
132
133
        if ($token->hasClaim('aud')) {
134
            $issuer = $token->getClaim('aud', false);
135
            if ($issuer !== $this->issuer) {
136
                throw new TokenAudienceException(sprintf(
137
                    'Token audience does not match'
138
                ));
139
            }
140
        }
141
142
        return $token;
143
    }
144
145
    public function verifySignature(Token $token): bool
146
    {
147
        return $token->verify($this->signer, $this->privateKey);
148
    }
149
150
    public function isExpired(Token $token): bool
151
    {
152
        return $token->isExpired();
153
    }
154
}
155