Failed Conditions
Push — master ( c6060f...bdc7d6 )
by Sébastien
02:20
created

TokenManager::signToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
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\BaseSigner;
16
use Lcobucci\JWT\Signer\Hmac\Sha256;
17
use Lcobucci\JWT\Token;
18
use Lcobucci\JWT\ValidationData;
19
use Ramsey\Uuid\Uuid;
20
use SebastianFeldmann\Git\Command\Base;
21
22
class TokenManager
23
{
24
	const DEFAULT_EXPIRY = 3600;
25
26
	/**
27
	 * @var BaseSigner
28
	 */
29
    private $signer;
30
	/**
31
	 * @var string
32
	 */
33
    private $issuer;
34
	/**
35
	 * @var string
36
	 */
37
    private $audience;
38
	/**
39
	 * @var string
40
	 */
41
    private $privateKey;
42
43
	/**
44
	 * @var int
45
	 */
46
    private $defaultExpiry;
47
48
    public function __construct(string $privateKey, int $defaultExpiry = self::DEFAULT_EXPIRY)
49
    {
50
        $this->signer     = $this->getSigner();
51
        $this->issuer     = $_SERVER['SERVER_NAME'];
52
        $this->audience   = $_SERVER['SERVER_NAME'];
53
        $this->privateKey = $privateKey;
54
        $this->defaultExpiry = $defaultExpiry;
55
    }
56
57
    public function createNewToken(array $customClaims = [], int $expiration = 3600, bool $autoSign = true): Token
58
    {
59
        $builder = (new Builder())
60
            ->setIssuer($this->issuer) // Configures the issuer (iss claim)
61
            ->setAudience($this->audience) // Configures the audience (aud claim)
62
            ->setId(Uuid::uuid1()->toString(), true) // Configures the id (jti claim), replicating as a header item
63
            ->setIssuedAt(time()) // Configures the time that the token was issue (iat claim)
64
            ->setNotBefore(time() + 0) // Configures the time that the token can be used (nbf claim)
65
            ->setExpiration(time() + $expiration); // Configures the expiration time of the token (exp claim)
66
67
        foreach ($customClaims as $key => $value) {
68
            $builder->set($key, $value);
69
        }
70
71
        if ($autoSign) {
72
            return $this->signToken($builder);
73
        }
74
75
        return $builder->getToken();
76
    }
77
78
    /**
79
     * Sign the token.
80
     */
81
    public function signToken(Builder $builder): Token
82
    {
83
        return $builder->sign($this->signer, $this->privateKey)
84
            ->getToken(); // Retrieves the generated token
85
    }
86
87
    /**
88
     * @throws TokenParseException
89
     */
90
    public function parseToken(string $tokenString): Token
91
    {
92
        $tokenParser = new Parser();
93
        try {
94
            return $tokenParser->parse($tokenString);
95
        } catch (\Throwable $e) {
96
            throw new TokenParseException($e->getMessage());
97
        }
98
    }
99
100
    /**
101
     * Ensure that the token signature is valid and
102
     * the token have not been tampered.
103
     *
104
     * @throws TokenSignatureException
105
     */
106
    public function ensureValidSignature(Token $token): void
107
    {
108
        if (!$this->verifySignature($token)) {
109
            throw new TokenSignatureException(sprintf(
110
                'Token failed signature verification.'
111
            ));
112
        }
113
    }
114
115
    /**
116
     * @throw TokenExpiredException
117
     */
118
    public function ensureNotExpired(Token $token): void
119
    {
120
        if ($this->isExpired($token)) {
121
            throw new TokenExpiredException(sprintf(
122
                'Token validity has expired.'
123
            ));
124
        }
125
    }
126
127
    /**
128
     * @throws TokenValidationExceptionInterface the main one
129
     * @throws TokenParseException
130
     * @throws TokenExpiredException
131
     * @throws TokenSignatureException
132
     * @throws TokenIssuerException
133
     * @throws TokenAudienceException
134
     */
135
    public function getValidatedToken(string $tokenString): Token
136
    {
137
        $token = $this->parseToken($tokenString);
138
139
        $this->ensureValidSignature($token);
140
        $this->ensureNotExpired($token);
141
142
        $data = new ValidationData(); // It will use the current time to validate (iat, nbf and exp)
143
        $data->setIssuer($this->issuer);
144
        $data->setAudience($this->audience);
145
146
        if ($token->hasClaim('iss')) {
147
            $issuer = $token->getClaim('iss', false);
148
            if ($issuer !== $this->issuer) {
149
                throw new TokenIssuerException(sprintf(
150
                    'Token issuer does not match'
151
                ));
152
            }
153
        }
154
155
        if ($token->hasClaim('aud')) {
156
            $issuer = $token->getClaim('aud', false);
157
            if ($issuer !== $this->issuer) {
158
                throw new TokenAudienceException(sprintf(
159
                    'Token audience does not match'
160
                ));
161
            }
162
        }
163
164
        return $token;
165
    }
166
167
    public function getSigner(): BaseSigner {
168
    	return new Sha256();
169
	}
170
171
    public function verifySignature(Token $token): bool
172
    {
173
        return $token->verify($this->signer, $this->privateKey);
174
    }
175
176
    public function isExpired(Token $token): bool
177
    {
178
        return $token->isExpired();
179
    }
180
}
181