@@ -62,7 +62,7 @@ public async Task RunAsync(MyApp.IOConsole ioConsole)
6262 input = ( ioConsole . ReadLine ( ) ?? "" ) . ToLower ( ) ;
6363
6464 var exchange = "0" ;
65- if ( input . EndsWith ( "x" ) || input . EndsWith ( "1" ) || input . EndsWith ( "2" ) )
65+ if ( input . EndsWith ( "x" ) || input . EndsWith ( "1" ) || input . EndsWith ( "2" ) || input . EndsWith ( "3" ) )
6666 {
6767 exchange = input . Substring ( 1 , 1 ) ;
6868 input = input . Substring ( 0 , 1 ) ;
@@ -320,7 +320,160 @@ public async Task RunAsync(MyApp.IOConsole ioConsole)
320320 }
321321 }
322322
323- if ( exchange == "2" )
323+ if ( exchange == "2" || exchange == "3" )
324+ {
325+ handler = new HttpClientHandler { DefaultProxyCredentials = CredentialCache . DefaultCredentials } ;
326+ client = new HttpClient ( handler ) ;
327+
328+ ioConsole . WriteLine ( "Token Exchange" ) ;
329+ configUrlList = [
330+ "https://iam-security-training.com/provider/sts"
331+ ] ;
332+ for ( var i = 0 ; i < configUrlList . Count ; i ++ )
333+ {
334+ ioConsole . WriteLine ( i + ": " + configUrlList [ i ] ) ;
335+ }
336+ input = "0" ;
337+ if ( configUrlList . Count > 1 )
338+ {
339+ ioConsole . WriteLine ( "Enter index: " ) ;
340+ input = ioConsole . ReadLine ( ) ;
341+ }
342+ configUrl = configUrlList [ Convert . ToInt32 ( input ?? "0" ) ] ;
343+
344+ configUrlList = [
345+ "" ,
346+ "basyx" ,
347+ "assetfox" ,
348+ "factory-x"
349+ ] ;
350+ for ( var i = 0 ; i < configUrlList . Count ; i ++ )
351+ {
352+ ioConsole . WriteLine ( i + ": " + configUrlList [ i ] ) ;
353+ }
354+ input = "0" ;
355+ if ( configUrlList . Count > 1 )
356+ {
357+ ioConsole . WriteLine ( "Enter index: " ) ;
358+ input = ioConsole . ReadLine ( ) ;
359+ }
360+ target = configUrlList [ Convert . ToInt32 ( input ?? "0" ) ] ;
361+
362+ var d = new Dictionary < string , string >
363+ {
364+ { "grant_type" , "urn:ietf:params:oauth:grant-type:token-exchange" } ,
365+ { "subject_token_type" , "urn:ietf:params:oauth:token-type:jwt" } ,
366+ { "requested_token_type" , "urn:ietf:params:oauth:token-type:access_token" } ,
367+ { "subject_token" , accessToken } ,
368+ } ;
369+ if ( target != "" )
370+ {
371+ d . Add ( "audience" , target ) ;
372+ }
373+ var request = new HttpRequestMessage ( HttpMethod . Post , $ "{ configUrl } /token")
374+ {
375+ Content = new FormUrlEncodedContent ( d )
376+ } ;
377+ request . Content . Headers . ContentType = new MediaTypeHeaderValue ( "application/x-www-form-urlencoded" ) ;
378+
379+ var response = await client . SendAsync ( request ) ;
380+ var content = await response . Content . ReadAsStringAsync ( ) ;
381+
382+ accessToken = "" ;
383+ doc = JsonDocument . Parse ( content ) ;
384+ if ( doc . RootElement . TryGetProperty ( "access_token" , out var tokenElement ) )
385+ {
386+ accessToken = tokenElement . GetString ( ) ;
387+ ioConsole . WriteLine ( "Access Token: " + accessToken ) ;
388+
389+ using var httpClient = new HttpClient ( handler ) ;
390+ var jwksJson = await httpClient . GetStringAsync ( $ "{ configUrl } /jwks") ;
391+ var jwks = JObject . Parse ( jwksJson ) [ "keys" ] ;
392+
393+ /*
394+ var handler2 = new JwtSecurityTokenHandler();
395+ var jwt2 = handler2.ReadJwtToken(accessToken);
396+ var kid = jwt2.Header["kid"].ToString();
397+
398+ // 3. Find matching key
399+ var key = jwks.First(k => k["kid"].ToString() == kid);
400+
401+ // 4. Build RSA key
402+ var e = Base64UrlEncoder.DecodeBytes(key["e"].ToString());
403+ var n = Base64UrlEncoder.DecodeBytes(key["n"].ToString());
404+ var rsa = new RSAParameters { Exponent = e, Modulus = n };
405+ var rsaKey = new RsaSecurityKey(rsa);
406+
407+ // 5. Validate token
408+ var validationParams = new TokenValidationParameters
409+ {
410+ ValidateIssuer = false,
411+ ValidateAudience = false,
412+ ValidateLifetime = true,
413+ ValidateIssuerSigningKey = true,
414+ IssuerSigningKey = rsaKey,
415+ ClockSkew = TimeSpan.FromMinutes(5)
416+ };
417+
418+ try
419+ {
420+ handler2.ValidateToken(accessToken, validationParams, out _);
421+ ioConsole.WriteLine("Token is valid");
422+ }
423+ catch (Exception ex)
424+ {
425+ ioConsole.WriteLine($"Validation failed: {ex.Message}");
426+ }
427+ */
428+
429+ // 1) Handler
430+ var handler2 = new JsonWebTokenHandler ( ) ;
431+
432+ // 2) JWK aus JWKS direkt verwenden (hier exemplarisch LINQ auf Dein jwks-Array)
433+ var jwtHeaderKid = new JsonWebToken ( accessToken ) . Kid ; // liest 'kid' robust
434+ var jwkJson = jwks . First ( k => k [ "kid" ] . ToString ( ) == jwtHeaderKid ) . ToString ( ) ; // k ist i. d. R. ein JObject
435+ var jwk = new JsonWebKey ( jwkJson ) ;
436+
437+ // 3) Validierungsparameter
438+ var validationParams = new TokenValidationParameters
439+ {
440+ // Signaturprüfung
441+ IssuerSigningKey = jwk , // kein manuelles RSAParameters nötig
442+ ValidateIssuerSigningKey = true ,
443+
444+ // Lebenszeit
445+ ValidateLifetime = true ,
446+ RequireExpirationTime = true ,
447+ ClockSkew = TimeSpan . FromMinutes ( 5 ) , // bei UTC+0 gut, ggf. 2–5 Minuten
448+
449+ // Issuer/Audience je nach Bedarf (bei Tests oft aus)
450+ ValidateIssuer = false , // später auf true + ValidIssuer setzen
451+ ValidateAudience = false , // später auf true + ValidAudience setzen
452+
453+ // Keine Legacy-Claim-Mappings
454+ // MapInboundClaims = false,
455+
456+ // Optional: Name-/Rollen-Claims aus dem JWT
457+ NameClaimType = "name" ,
458+ RoleClaimType = "role"
459+ } ;
460+
461+ try
462+ {
463+ var result = handler2 . ValidateToken ( accessToken , validationParams ) ;
464+ if ( ! result . IsValid )
465+ ioConsole . WriteLine ( $ "Validation failed: { result . Exception ? . Message } ") ;
466+ else
467+ ioConsole . WriteLine ( "Token is valid" ) ;
468+ }
469+ catch ( Exception ex )
470+ {
471+ ioConsole . WriteLine ( $ "Validation failed: { ex . Message } ") ;
472+ }
473+ }
474+ }
475+
476+ if ( exchange == "3" )
324477 {
325478 handler = new HttpClientHandler { DefaultProxyCredentials = CredentialCache . DefaultCredentials } ;
326479 client = new HttpClient ( handler ) ;
0 commit comments