@@ -121,35 +121,69 @@ public function buildAuthorizationUrl(string $authorizationEndpoint, array $extr
121121 /**
122122 * Inspired by https://github.com/snake/moodle/compare/880462a1685...MDL-77077-master
123123 *
124- * @param array $jwks
125- * @param string $jwt
126- * @return array
127- * @throws \Exception
124+ * @param array $jwks The JSON Web Key Set
125+ * @param string $jwt The JWT token
126+ * @return array The modified JWKS
127+ * @throws \RuntimeException if no matching key is found or algorithm is unsupported
128128 */
129129 private function fixJwksAlg (array $ jwks , string $ jwt ): array {
130- $ jwtParts = explode ('. ' , $ jwt );
131- $ jwtHeader = json_decode (JWT ::urlsafeB64Decode ($ jwtParts [0 ]), true );
132- if (!isset ($ jwtHeader ['kid ' ])) {
133- throw new \Exception ('Error: kid must be provided in JWT header. ' );
130+ $ jwtParts = explode ('. ' , $ jwt , 3 );
131+ $ header = json_decode (JWT ::urlsafeB64Decode ($ jwtParts [0 ]), true );
132+ $ kid = $ header ['kid ' ] ?? null ;
133+ $ alg = $ header ['alg ' ] ?? null ;
134+
135+ $ expectedKty = self ::SUPPORTED_JWK_ALGS [$ alg ] ?? null ;
136+ if ($ expectedKty === null ) {
137+ throw new \RuntimeException ('Unsupported JWT alg: ' . ($ alg ?? 'unknown ' ));
138+ }
139+
140+ $ keys = $ jwks ['keys ' ] ?? null ;
141+ if (!is_array ($ keys )) {
142+ throw new \RuntimeException ('Invalid JWKS: missing "keys" array ' );
134143 }
135144
136- foreach ($ jwks ['keys ' ] as $ index => $ key ) {
137- // Only fix the key being referred to in the JWT.
138- if ($ jwtHeader ['kid ' ] != $ key ['kid ' ]) {
145+ $ matchingIndex = null ;
146+
147+ foreach ($ keys as $ index => $ key ) {
148+ $ keyKty = $ key ['kty ' ] ?? null ;
149+ $ keyUse = $ key ['use ' ] ?? null ;
150+
151+ // Skip keys with incompatible type
152+ if ($ keyKty !== $ expectedKty ) {
139153 continue ;
140154 }
141155
142- // Only fix the key if the alg is missing.
143- if (! empty ( $ key [ ' alg ' ]) ) {
156+ // Skip keys not intended for signature
157+ if ($ keyUse !== null && $ keyUse !== ' sig ' ) {
144158 continue ;
145159 }
146160
147- // The header alg must match the key type (family) specified in the JWK's kty.
148- if (!isset (self ::SUPPORTED_JWK_ALGS [$ jwtHeader ['alg ' ]]) || self ::SUPPORTED_JWK_ALGS [$ jwtHeader ['alg ' ]] !== $ key ['kty ' ]) {
149- throw new \Exception ('Error: Alg specified in the JWT header is incompatible with the JWK key type ' );
161+ // If JWT has a kid, match strictly
162+ if ($ kid !== null ) {
163+ if (($ key ['kid ' ] ?? null ) !== $ kid ) {
164+ continue ;
165+ }
166+ $ matchingIndex = $ index ;
167+ break ;
150168 }
151169
152- $ jwks ['keys ' ][$ index ]['alg ' ] = $ jwtHeader ['alg ' ];
170+ // If no kid, select the first compatible key
171+ if ($ matchingIndex === null ) {
172+ $ matchingIndex = $ index ;
173+ }
174+ }
175+
176+ if ($ matchingIndex === null ) {
177+ throw new \RuntimeException (sprintf (
178+ 'No matching key found in JWKS (alg=%s, kid=%s) ' ,
179+ $ alg ?? 'unknown ' ,
180+ $ kid ?? 'none '
181+ ));
182+ }
183+
184+ // Set 'alg' field if missing
185+ if (empty ($ jwks ['keys ' ][$ matchingIndex ]['alg ' ])) {
186+ $ jwks ['keys ' ][$ matchingIndex ]['alg ' ] = $ alg ;
153187 }
154188
155189 return $ jwks ;
0 commit comments