@@ -121,44 +121,71 @@ 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- $ kid = $ jwtHeader ['kid ' ] ?? null ;
133- $ alg = $ jwtHeader ['alg ' ] ?? null ;
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+ }
134139
135- if (!isset (self ::SUPPORTED_JWK_ALGS [$ alg ])) {
136- throw new \Exception ('Unsupported JWT alg: ' . ($ alg ?? 'unknown ' ));
140+ $ keys = $ jwks ['keys ' ] ?? null ;
141+ if (!is_array ($ keys )) {
142+ throw new \RuntimeException ('Invalid JWKS: missing "keys" array ' );
137143 }
138144
139- $ expectedKty = self ::SUPPORTED_JWK_ALGS [$ alg ];
145+ $ matchingIndex = null ;
146+
147+ foreach ($ keys as $ index => $ key ) {
148+ $ keyKty = $ key ['kty ' ] ?? null ;
149+ $ keyUse = $ key ['use ' ] ?? null ;
140150
141- foreach ($ jwks ['keys ' ] as $ index => $ key ) {
142- // Skip if alg is already set
143- if (!empty ($ key ['alg ' ])) {
151+ // Skip keys with incompatible type
152+ if ($ keyKty !== $ expectedKty ) {
144153 continue ;
145154 }
146155
147- // Match by kid if present, or by alg and kty if kid is missing
148- if ($ kid !== null && isset ( $ key [ ' kid ' ]) && $ key [ ' kid ' ] !== $ kid ) {
156+ // Skip keys not intended for signature
157+ if ($ keyUse !== null && $ keyUse !== ' sig ' ) {
149158 continue ;
150159 }
151160
152- // Validate algorithm compatibility
153- if (($ key ['kty ' ] ?? null ) !== $ expectedKty ) {
154- continue ;
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 ;
155168 }
156169
157- // Set alg for the matching key
158- $ jwks ['keys ' ][$ index ]['alg ' ] = $ alg ;
159- return $ jwks ;
170+ // If no kid, select the first compatible key
171+ if ($ matchingIndex === null ) {
172+ $ matchingIndex = $ index ;
173+ }
160174 }
161-
162- throw new \Exception ('No matching key found in JWKS (alg= ' . ($ alg ?? 'unknown ' ) . ', kid= ' . ($ kid ?? 'none ' ) . ') ' );
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 ;
187+ }
188+
189+ return $ jwks ;
163190 }
164191}
0 commit comments