diff --git a/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs b/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs index bf18e8f3..663ec512 100644 --- a/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs +++ b/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs @@ -39,8 +39,6 @@ public class InteractiveService : BackgroundService try { - ClaimsPrincipal principal; - // Resolve the server configuration and determine the type of flow // to use depending on the supported grants and the user selection. var configuration = await _service.GetServerConfigurationByProviderNameAsync(provider, stoppingToken); @@ -75,14 +73,30 @@ public class InteractiveService : BackgroundService AnsiConsole.MarkupLine("[cyan]Waiting for the user to approve the authorization demand.[/]"); // Wait for the user to complete the demand on the other device. - principal = (await _service.AuthenticateWithDeviceAsync(new() + var response = await _service.AuthenticateWithDeviceAsync(new() { CancellationToken = stoppingToken, DeviceCode = result.DeviceCode, Interval = result.Interval, ProviderName = provider, Timeout = result.ExpiresIn < TimeSpan.FromMinutes(5) ? result.ExpiresIn : TimeSpan.FromMinutes(5) - })).Principal; + }); + + AnsiConsole.MarkupLine("[green]Device authentication successful:[/]"); + AnsiConsole.Write(CreateClaimTable(response.Principal)); + + // If a refresh token was returned by the authorization server, ask the user + // if the access token should be refreshed using the refresh_token grant. + if (!string.IsNullOrEmpty(response.RefreshToken) && await UseRefreshTokenGrantAsync(stoppingToken)) + { + AnsiConsole.MarkupLine("[green]Token refreshing successful:[/]"); + AnsiConsole.Write(CreateClaimTable((await _service.AuthenticateWithRefreshTokenAsync(new() + { + CancellationToken = stoppingToken, + ProviderName = provider, + RefreshToken = response.RefreshToken + })).Principal)); + } } else @@ -99,29 +113,28 @@ public class InteractiveService : BackgroundService AnsiConsole.MarkupLine("[cyan]Waiting for the user to approve the authorization demand.[/]"); // Wait for the user to complete the authorization process. - principal = (await _service.AuthenticateInteractivelyAsync(new() + var response = await _service.AuthenticateInteractivelyAsync(new() { CancellationToken = stoppingToken, Nonce = result.Nonce - })).Principal; - } - - AnsiConsole.MarkupLine("[green]Authentication successful:[/]"); + }); - var table = new Table() - .AddColumn(new TableColumn("Claim type").Centered()) - .AddColumn(new TableColumn("Claim value type").Centered()) - .AddColumn(new TableColumn("Claim value").Centered()); + AnsiConsole.MarkupLine("[green]Interactive authentication successful:[/]"); + AnsiConsole.Write(CreateClaimTable(response.Principal)); - foreach (var claim in principal.Claims) - { - table.AddRow( - claim.Type.EscapeMarkup(), - claim.ValueType.EscapeMarkup(), - claim.Value.EscapeMarkup()); + // If a refresh token was returned by the authorization server, ask the user + // if the access token should be refreshed using the refresh_token grant. + if (!string.IsNullOrEmpty(response.RefreshToken) && await UseRefreshTokenGrantAsync(stoppingToken)) + { + AnsiConsole.MarkupLine("[green]Token refreshing successful:[/]"); + AnsiConsole.Write(CreateClaimTable((await _service.AuthenticateWithRefreshTokenAsync(new() + { + CancellationToken = stoppingToken, + ProviderName = provider, + RefreshToken = response.RefreshToken + })).Principal)); + } } - - AnsiConsole.Write(table); } catch (OperationCanceledException) @@ -140,6 +153,25 @@ public class InteractiveService : BackgroundService } } + static Table CreateClaimTable(ClaimsPrincipal principal) + { + var table = new Table() + .LeftAligned() + .AddColumn("Claim type") + .AddColumn("Claim value type") + .AddColumn("Claim value"); + + foreach (var claim in principal.Claims) + { + table.AddRow( + claim.Type.EscapeMarkup(), + claim.ValueType.EscapeMarkup(), + claim.Value.EscapeMarkup()); + } + + return table; + } + static Task UseDeviceAuthorizationGrantAsync(CancellationToken cancellationToken) { static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt( @@ -152,6 +184,18 @@ public class InteractiveService : BackgroundService return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken); } + static Task UseRefreshTokenGrantAsync(CancellationToken cancellationToken) + { + static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt( + "Would you like to refresh the user authentication using the refresh token grant?") + { + DefaultValue = false, + ShowDefaultValue = true + }); + + return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken); + } + Task GetSelectedProviderAsync(CancellationToken cancellationToken) { async Task PromptAsync() => AnsiConsole.Prompt(new SelectionPrompt() diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs index 44a49c70..807ff6d2 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs @@ -132,7 +132,7 @@ public static partial class OpenIddictServerHandlers parameters.IssuerSigningKeyResolver = (_, token, _, _) => Task.Run(async () => { // Resolve the client application corresponding to the token issuer and retrieve - // the signing keys from to the JSON Web Key set attached to the client application. + // the signing keys from the JSON Web Key set attached to the client application. // // Important: at this stage, the issuer isn't guaranteed to be valid or legitimate. var application = await _applicationManager.FindByClientIdAsync(token.Issuer); diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 836d3ac8..9c589d02 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -1078,7 +1078,7 @@ public static partial class OpenIddictServerHandlers Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - // Don't validate the client type on endpoint that don't support client authentication. + // Don't validate the client type on endpoints that don't support client authentication. if (context.EndpointType is OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout or OpenIddictServerEndpointType.Userinfo or @@ -1187,7 +1187,7 @@ public static partial class OpenIddictServerHandlers Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret)); - // Don't validate the client secret on endpoint that don't support client authentication. + // Don't validate the client secret on endpoints that don't support client authentication. if (context.EndpointType is OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout or OpenIddictServerEndpointType.Userinfo or