Browse Source

Update the console sandbox to support testing the client credentials and resource owner password credentials grants

pull/2023/head
Kévin Chalet 2 years ago
parent
commit
c3bb97aa8d
  1. 160
      sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs
  2. 7
      sandbox/OpenIddict.Sandbox.Console.Client/Program.cs
  3. 11
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  4. 12
      src/OpenIddict.Client/OpenIddictClientService.cs

160
sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs

@ -39,12 +39,41 @@ public class InteractiveService : BackgroundService
try
{
// 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);
if (configuration.GrantTypesSupported.Contains(GrantTypes.DeviceCode) &&
configuration.DeviceAuthorizationEndpoint is not null &&
await UseDeviceAuthorizationGrantAsync(stoppingToken))
var type = await GetSelectedGrantTypeAsync(provider, stoppingToken);
if (type is GrantTypes.ClientCredentials)
{
AnsiConsole.MarkupLine("[cyan]Sending the token request.[/]");
// Ask OpenIddict to authenticate the client application using the client credentials grant.
await _service.AuthenticateWithClientCredentialsAsync(new()
{
CancellationToken = stoppingToken,
ProviderName = provider
});
AnsiConsole.MarkupLine("[green]Client credentials authentication successful.[/]");
}
else if (type is GrantTypes.Password)
{
var (username, password) = (await GetUsernameAsync(stoppingToken), await GetPasswordAsync(stoppingToken));
AnsiConsole.MarkupLine("[cyan]Sending the token request.[/]");
// Ask OpenIddict to authenticate the user using the resource owner password credentials grant.
var response = await _service.AuthenticateWithPasswordAsync(new()
{
CancellationToken = stoppingToken,
ProviderName = provider,
Username = username,
Password = password
});
AnsiConsole.MarkupLine("[green]Resource owner password credentials authentication successful:[/]");
AnsiConsole.Write(CreateClaimTable(response.Principal));
}
else if (type is GrantTypes.DeviceCode)
{
// Ask OpenIddict to send a device authorization request and write
// the complete verification endpoint URI to the console output.
@ -86,6 +115,7 @@ public class InteractiveService : BackgroundService
AnsiConsole.Write(CreateClaimTable(response.Principal));
// If introspection is supported by the server, ask the user if the access token should be introspected.
var configuration = await _service.GetServerConfigurationByProviderNameAsync(provider, stoppingToken);
if (configuration.IntrospectionEndpoint is not null && await IntrospectAccessTokenAsync(stoppingToken))
{
AnsiConsole.MarkupLine("[steelblue]Claims extracted from the token introspection response:[/]");
@ -114,7 +144,7 @@ public class InteractiveService : BackgroundService
// 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))
if (!string.IsNullOrEmpty(response.RefreshToken) && await RefreshTokenAsync(stoppingToken))
{
AnsiConsole.MarkupLine("[steelblue]Claims extracted from the refreshed identity:[/]");
AnsiConsole.Write(CreateClaimTable((await _service.AuthenticateWithRefreshTokenAsync(new()
@ -126,7 +156,7 @@ public class InteractiveService : BackgroundService
}
}
else
else if (type is GrantTypes.AuthorizationCode)
{
AnsiConsole.MarkupLine("[cyan]Launching the system browser.[/]");
@ -152,6 +182,7 @@ public class InteractiveService : BackgroundService
// If an access token was returned by the authorization server and introspection is
// supported by the server, ask the user if the access token should be introspected.
var configuration = await _service.GetServerConfigurationByProviderNameAsync(provider, stoppingToken);
if (!string.IsNullOrEmpty(response.BackchannelAccessToken) &&
configuration.IntrospectionEndpoint is not null &&
await IntrospectAccessTokenAsync(stoppingToken))
@ -185,7 +216,7 @@ public class InteractiveService : BackgroundService
// 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))
if (!string.IsNullOrEmpty(response.RefreshToken) && await RefreshTokenAsync(stoppingToken))
{
AnsiConsole.MarkupLine("[steelblue]Claims extracted from the refreshed identity:[/]");
AnsiConsole.Write(CreateClaimTable((await _service.AuthenticateWithRefreshTokenAsync(new()
@ -264,35 +295,93 @@ public class InteractiveService : BackgroundService
return table;
}
static Task<bool> IntrospectAccessTokenAsync(CancellationToken cancellationToken)
Task<string> GetSelectedProviderAsync(CancellationToken cancellationToken)
{
static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt(
"Would you like to introspect the access token?")
async Task<string> PromptAsync() => AnsiConsole.Prompt(new SelectionPrompt<OpenIddictClientRegistration>()
.Title("Select the authentication provider you'd like to log in with.")
.AddChoices(from registration in await _service.GetClientRegistrationsAsync(stoppingToken)
where !string.IsNullOrEmpty(registration.ProviderName)
where !string.IsNullOrEmpty(registration.ProviderDisplayName)
select registration)
.UseConverter(registration => registration.ProviderDisplayName!)).ProviderName!;
return WaitAsync(Task.Run(PromptAsync, cancellationToken), cancellationToken);
}
Task<string> GetSelectedGrantTypeAsync(string provider, CancellationToken cancellationToken)
{
async Task<string> PromptAsync()
{
Comparer = StringComparer.CurrentCultureIgnoreCase,
DefaultValue = false,
ShowDefaultValue = true
List<(string GrantType, string DisplayName)> choices = [];
var configuration = await _service.GetServerConfigurationByProviderNameAsync(provider, stoppingToken);
if (configuration.GrantTypesSupported.Contains(GrantTypes.AuthorizationCode) &&
configuration.AuthorizationEndpoint is not null &&
configuration.TokenEndpoint is not null)
{
choices.Add((GrantTypes.AuthorizationCode, "Authorization code grant"));
}
if (configuration.GrantTypesSupported.Contains(GrantTypes.DeviceCode) &&
configuration.DeviceAuthorizationEndpoint is not null &&
configuration.TokenEndpoint is not null)
{
choices.Add((GrantTypes.DeviceCode, "Device authorization code grant"));
}
if (configuration.GrantTypesSupported.Contains(GrantTypes.Password) &&
configuration.TokenEndpoint is not null)
{
choices.Add((GrantTypes.Password, "Resource owner password credentials grant"));
}
if (configuration.GrantTypesSupported.Contains(GrantTypes.ClientCredentials) &&
configuration.TokenEndpoint is not null)
{
choices.Add((GrantTypes.ClientCredentials, "Client credentials grant (application authentication only)"));
}
if (choices.Count is 1)
{
return choices[0].GrantType;
}
return AnsiConsole.Prompt(new SelectionPrompt<(string GrantType, string DisplayName)>()
.Title("Select the grant type you'd like to use.")
.AddChoices(choices)
.UseConverter(choice => choice.DisplayName)).GrantType;
}
return WaitAsync(Task.Run(PromptAsync, cancellationToken), cancellationToken);
}
Task<string> GetUsernameAsync(CancellationToken cancellationToken)
{
static string Prompt() => AnsiConsole.Prompt(new TextPrompt<string>("Please enter your username:")
{
AllowEmpty = false,
IsSecret = false
});
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
}
static Task<bool> LogOutAsync(CancellationToken cancellationToken)
Task<string> GetPasswordAsync(CancellationToken cancellationToken)
{
static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt("Would you like to log out?")
static string Prompt() => AnsiConsole.Prompt(new TextPrompt<string>("Please enter your password:")
{
Comparer = StringComparer.CurrentCultureIgnoreCase,
DefaultValue = false,
ShowDefaultValue = true
AllowEmpty = false,
IsSecret = true
});
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
}
static Task<bool> RevokeAccessTokenAsync(CancellationToken cancellationToken)
static Task<bool> RefreshTokenAsync(CancellationToken cancellationToken)
{
static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt(
"Would you like to revoke the access token?")
"Would you like to refresh the user authentication using the refresh token grant?")
{
Comparer = StringComparer.CurrentCultureIgnoreCase,
DefaultValue = false,
@ -302,10 +391,10 @@ public class InteractiveService : BackgroundService
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
}
static Task<bool> UseDeviceAuthorizationGrantAsync(CancellationToken cancellationToken)
static Task<bool> IntrospectAccessTokenAsync(CancellationToken cancellationToken)
{
static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt(
"Would you like to authenticate using the device authorization grant?")
"Would you like to introspect the access token?")
{
Comparer = StringComparer.CurrentCultureIgnoreCase,
DefaultValue = false,
@ -315,10 +404,9 @@ public class InteractiveService : BackgroundService
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
}
static Task<bool> UseRefreshTokenGrantAsync(CancellationToken cancellationToken)
static Task<bool> LogOutAsync(CancellationToken cancellationToken)
{
static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt(
"Would you like to refresh the user authentication using the refresh token grant?")
static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt("Would you like to log out?")
{
Comparer = StringComparer.CurrentCultureIgnoreCase,
DefaultValue = false,
@ -328,17 +416,17 @@ public class InteractiveService : BackgroundService
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
}
Task<string> GetSelectedProviderAsync(CancellationToken cancellationToken)
static Task<bool> RevokeAccessTokenAsync(CancellationToken cancellationToken)
{
async Task<string> PromptAsync() => AnsiConsole.Prompt(new SelectionPrompt<OpenIddictClientRegistration>()
.Title("Select the authentication provider you'd like to log in with.")
.AddChoices(from registration in await _service.GetClientRegistrationsAsync(stoppingToken)
where !string.IsNullOrEmpty(registration.ProviderName)
where !string.IsNullOrEmpty(registration.ProviderDisplayName)
select registration)
.UseConverter(registration => registration.ProviderDisplayName!)).ProviderName!;
static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt(
"Would you like to revoke the access token?")
{
Comparer = StringComparer.CurrentCultureIgnoreCase,
DefaultValue = false,
ShowDefaultValue = true
});
return WaitAsync(Task.Run(PromptAsync, cancellationToken), cancellationToken);
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
}
static async Task<T> WaitAsync<T>(Task<T> task, CancellationToken cancellationToken)

7
sandbox/OpenIddict.Sandbox.Console.Client/Program.cs

@ -36,10 +36,13 @@ var host = new HostBuilder()
// Register the OpenIddict client components.
.AddClient(options =>
{
// Note: this sample uses the authorization code, device authorization code
// and refresh token flows, but you can enable the other flows if necessary.
// Note: this sample uses the authorization code, client credentials,
// device authorization code, refresh token and resource owner password
// credentials flows, but you can enable the other flows if necessary.
options.AllowAuthorizationCodeFlow()
.AllowClientCredentialsFlow()
.AllowDeviceCodeFlow()
.AllowPasswordFlow()
.AllowRefreshTokenFlow();
// Register the signing and encryption credentials used to protect

11
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1420,7 +1420,10 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<value>Only instances of type '{0}' can be used as primary HTTP handlers for the HTTP clients managed by OpenIddict.</value>
</data>
<data name="ID0374" xml:space="preserve">
<value>An error occurred while authenticating the user.</value>
<value>An error occurred while authenticating the user.
Error: {0}
Error description: {1}
Error URI: {2}</value>
</data>
<data name="ID0375" xml:space="preserve">
<value>The protocol activation cannot be resolved or contains invalid data.</value>
@ -1632,6 +1635,12 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<data name="ID0434" xml:space="preserve">
<value>An error occurred while signing the user out.</value>
</data>
<data name="ID0435" xml:space="preserve">
<value>An error occurred while authenticating the client application.
Error: {0}
Error description: {1}
Error URI: {2}</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>

12
src/OpenIddict.Client/OpenIddictClientService.cs

@ -311,7 +311,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new ProtocolException(
message: SR.GetResourceString(SR.ID0374),
SR.FormatID0374(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}
@ -412,7 +412,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new ProtocolException(
message: SR.GetResourceString(SR.ID0374),
SR.FormatID0374(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}
@ -499,7 +499,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new ProtocolException(
SR.FormatID0319(context.Error, context.ErrorDescription, context.ErrorUri),
SR.FormatID0435(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}
@ -604,7 +604,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new ProtocolException(
message: SR.GetResourceString(SR.ID0374),
SR.FormatID0374(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}
@ -718,7 +718,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new ProtocolException(
message: SR.GetResourceString(SR.ID0374),
SR.FormatID0374(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}
@ -810,7 +810,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new ProtocolException(
SR.FormatID0319(context.Error, context.ErrorDescription, context.ErrorUri),
SR.FormatID0374(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}

Loading…
Cancel
Save