Issues (4)

src/JwtAuthRoles.php (1 issue)

Labels
Severity
1
<?php
2
3
namespace Werk365\JwtAuthRoles;
4
5
use Firebase\JWT\JWT;
6
use Illuminate\Support\Facades\Http;
0 ignored issues
show
The type Illuminate\Support\Facades\Http was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Illuminate\Support\Str;
8
use phpseclib\Crypt\RSA;
9
use phpseclib\Math\BigInteger;
10
use Werk365\JwtAuthRoles\Exceptions\AuthException;
11
use Werk365\JwtAuthRoles\Models\JwtKey;
12
use Werk365\JwtAuthRoles\Models\JwtUser;
13
14
class JwtAuthRoles
15
{
16
    private static function getKid(string $jwt): ?string
17
    {
18
        if (! Str::is('*.*.*', $jwt)) {
19
            throw AuthException::auth(422, 'Malformed JWT');
20
        }
21
22
        $header = JWT::jsonDecode(JWT::urlsafeB64Decode(Str::before($jwt, '.')));
23
24
        if (isset($header->alg) && $header->alg !== config('jwtauthroles.alg')) {
25
            throw AuthException::auth(422, 'Invalid algorithm');
26
        }
27
28
        return $header->kid ?? null;
29
    }
30
31
    private static function getClaims(string $jwt): ?object
32
    {
33
        if (! Str::is('*.*.*', $jwt)) {
34
            throw AuthException::auth(422, 'Malformed JWT');
35
        }
36
37
        $claims = explode('.', $jwt);
38
        $claims = JWT::jsonDecode(JWT::urlsafeB64Decode($claims[1]));
39
40
        return $claims ?? null;
41
    }
42
43
    private static function jwkToPem(object $jwk): ?string
44
    {
45
        if (! isset($jwk->e) || ! isset($jwk->n)) {
46
            throw AuthException::auth(500, 'Malformed jwk');
47
        }
48
49
        $rsa = new RSA();
50
        $rsa->loadKey([
51
            'e' => new BigInteger(JWT::urlsafeB64Decode($jwk->e), 256),
52
            'n' => new BigInteger(JWT::urlsafeB64Decode($jwk->n), 256),
53
        ]);
54
55
        if ($rsa->getPublicKey() === false) {
56
            return null;
57
        }
58
59
        return $rsa->getPublicKey();
60
    }
61
62
    private static function getJwk(string $kid, string $uri): ?string
63
    {
64
        $response = Http::get($uri);
65
        $json = $response->getBody();
66
        if (! $json) {
67
            throw AuthException::auth(404, 'jwks endpoint not found');
68
        }
69
70
        $jwks = json_decode($json, false);
71
72
        if (! $jwks || ! isset($jwks->keys) || ! is_array($jwks->keys)) {
73
            throw AuthException::auth(404, 'No JWKs found');
74
        }
75
76
        foreach ($jwks->keys as $jwk) {
77
            if ($jwk->kid === $kid) {
78
                return self::jwkToPem($jwk);
79
            }
80
        }
81
82
        throw AuthException::auth(401, 'Unauthorized');
83
    }
84
85
    private static function getPem(string $kid, string $uri): ?string
86
    {
87
        $response = Http::get($uri);
88
        $json = $response->getBody();
89
        if (! $json) {
90
            throw AuthException::auth(404, 'pem endpoint not found');
91
        }
92
93
        $pems = json_decode($json, false);
94
95
        if (! $pems || ! isset($pems->publicKeys) || ! is_object($pems->publicKeys)) {
96
            throw AuthException::auth(404, 'pem not found');
97
        }
98
99
        foreach ($pems->publicKeys as $key=>$pem) {
100
            if ($key === $kid) {
101
                return $pem;
102
            }
103
        }
104
105
        throw AuthException::auth(401, 'Unauthorized');
106
    }
107
108
    private static function verifyToken(string $jwt, string $uri, bool $jwk = false): object
109
    {
110
        $kid = self::getKid($jwt);
111
        if (! $kid) {
112
            throw AuthException::auth(422, 'Malformed JWT');
113
        }
114
115
        $row = null;
116
117
        if (config('jwtauthroles.cache.enabled')) {
118
            if (config('jwtauthroles.cache.type') === 'database') {
119
                $row = JwtKey::where('kid', $kid)
120
                    ->orderBy('created_at', 'desc')
121
                    ->first('key');
122
            }
123
        }
124
125
        $publicKey = $row->key
126
            ?? $jwk
127
                ? self::getJwk($kid, $uri)
128
                : self::getPem($kid, $uri);
129
130
        if (! isset($publicKey) || ! $publicKey) {
131
            throw AuthException::auth(500, 'Unable to validate JWT');
132
        }
133
134
        if (config('jwtauthroles.cache.enabled')) {
135
            if (config('jwtauthroles.cache.type') === 'database' && ! $row) {
136
                JwtKey::create(['kid' => $kid, 'key' => $publicKey]);
137
            }
138
        }
139
140
        return JWT::decode($jwt, $publicKey, [config('jwtauthroles.alg')]);
141
    }
142
143
    public static function authUser(object $request)
144
    {
145
        $jwt = $request->bearerToken();
146
147
        $uri = config('jwtauthroles.useJwk')
148
            ? config('jwtauthroles.jwkUri')
149
            : config('jwtauthroles.pemUri');
150
151
        if (! config('jwtauthroles.validateJwt')) {
152
            $claims = self::getClaims($jwt);
153
        } else {
154
            $claims = self::verifyToken($jwt, $uri, config('jwtauthroles.useJwk'));
155
        }
156
157
        if (config('jwtauthroles.useDB')) {
158
            if (config('jwtauthroles.autoCreateUser')) {
159
                $user = JwtUser::firstOrNew([config('jwtauthroles.userId') => $claims->sub]);
160
                $user[config('jwtauthroles.userId')] = $claims->sub;
161
                $user->roles = json_encode($claims->roles);
162
                $user->claims = json_encode($claims);
163
                $user->save();
164
            } else {
165
                $user = JwtUser::where(config('jwtauthroles.userId'), '=', $claims->sub)->firstOrFail();
166
                $user->roles = json_encode($claims->roles);
167
                $user->claims = json_encode($claims);
168
                $user->save();
169
            }
170
        } else {
171
            $user = new JwtUser;
172
            $user->uuid = $claims->sub;
173
            $user->roles = $claims->roles;
174
            $user->claims = $claims;
175
        }
176
177
        return $user;
178
    }
179
}
180