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 try
{ {
// Resolve the server configuration and determine the type of flow var type = await GetSelectedGrantTypeAsync(provider, stoppingToken);
// to use depending on the supported grants and the user selection. if (type is GrantTypes.ClientCredentials)
var configuration = await _service.GetServerConfigurationByProviderNameAsync(provider, stoppingToken); {
if (configuration.GrantTypesSupported.Contains(GrantTypes.DeviceCode) && AnsiConsole.MarkupLine("[cyan]Sending the token request.[/]");
configuration.DeviceAuthorizationEndpoint is not null &&
await UseDeviceAuthorizationGrantAsync(stoppingToken)) // 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 // Ask OpenIddict to send a device authorization request and write
// the complete verification endpoint URI to the console output. // the complete verification endpoint URI to the console output.
@ -86,6 +115,7 @@ public class InteractiveService : BackgroundService
AnsiConsole.Write(CreateClaimTable(response.Principal)); AnsiConsole.Write(CreateClaimTable(response.Principal));
// If introspection is supported by the server, ask the user if the access token should be introspected. // 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)) if (configuration.IntrospectionEndpoint is not null && await IntrospectAccessTokenAsync(stoppingToken))
{ {
AnsiConsole.MarkupLine("[steelblue]Claims extracted from the token introspection response:[/]"); 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 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 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.MarkupLine("[steelblue]Claims extracted from the refreshed identity:[/]");
AnsiConsole.Write(CreateClaimTable((await _service.AuthenticateWithRefreshTokenAsync(new() 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.[/]"); 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 // 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. // 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) && if (!string.IsNullOrEmpty(response.BackchannelAccessToken) &&
configuration.IntrospectionEndpoint is not null && configuration.IntrospectionEndpoint is not null &&
await IntrospectAccessTokenAsync(stoppingToken)) 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 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 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.MarkupLine("[steelblue]Claims extracted from the refreshed identity:[/]");
AnsiConsole.Write(CreateClaimTable((await _service.AuthenticateWithRefreshTokenAsync(new() AnsiConsole.Write(CreateClaimTable((await _service.AuthenticateWithRefreshTokenAsync(new()
@ -264,35 +295,93 @@ public class InteractiveService : BackgroundService
return table; return table;
} }
static Task<bool> IntrospectAccessTokenAsync(CancellationToken cancellationToken) Task<string> GetSelectedProviderAsync(CancellationToken cancellationToken)
{ {
static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt( async Task<string> PromptAsync() => AnsiConsole.Prompt(new SelectionPrompt<OpenIddictClientRegistration>()
"Would you like to introspect the access token?") .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, List<(string GrantType, string DisplayName)> choices = [];
DefaultValue = false,
ShowDefaultValue = true 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); 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, AllowEmpty = false,
DefaultValue = false, IsSecret = true
ShowDefaultValue = true
}); });
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken); 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( 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, Comparer = StringComparer.CurrentCultureIgnoreCase,
DefaultValue = false, DefaultValue = false,
@ -302,10 +391,10 @@ public class InteractiveService : BackgroundService
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken); 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( 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, Comparer = StringComparer.CurrentCultureIgnoreCase,
DefaultValue = false, DefaultValue = false,
@ -315,10 +404,9 @@ public class InteractiveService : BackgroundService
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken); 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( static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt("Would you like to log out?")
"Would you like to refresh the user authentication using the refresh token grant?")
{ {
Comparer = StringComparer.CurrentCultureIgnoreCase, Comparer = StringComparer.CurrentCultureIgnoreCase,
DefaultValue = false, DefaultValue = false,
@ -328,17 +416,17 @@ public class InteractiveService : BackgroundService
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken); 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>() static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt(
.Title("Select the authentication provider you'd like to log in with.") "Would you like to revoke the access token?")
.AddChoices(from registration in await _service.GetClientRegistrationsAsync(stoppingToken) {
where !string.IsNullOrEmpty(registration.ProviderName) Comparer = StringComparer.CurrentCultureIgnoreCase,
where !string.IsNullOrEmpty(registration.ProviderDisplayName) DefaultValue = false,
select registration) ShowDefaultValue = true
.UseConverter(registration => registration.ProviderDisplayName!)).ProviderName!; });
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) 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. // Register the OpenIddict client components.
.AddClient(options => .AddClient(options =>
{ {
// Note: this sample uses the authorization code, device authorization code // Note: this sample uses the authorization code, client credentials,
// and refresh token flows, but you can enable the other flows if necessary. // device authorization code, refresh token and resource owner password
// credentials flows, but you can enable the other flows if necessary.
options.AllowAuthorizationCodeFlow() options.AllowAuthorizationCodeFlow()
.AllowClientCredentialsFlow()
.AllowDeviceCodeFlow() .AllowDeviceCodeFlow()
.AllowPasswordFlow()
.AllowRefreshTokenFlow(); .AllowRefreshTokenFlow();
// Register the signing and encryption credentials used to protect // 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> <value>Only instances of type '{0}' can be used as primary HTTP handlers for the HTTP clients managed by OpenIddict.</value>
</data> </data>
<data name="ID0374" xml:space="preserve"> <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>
<data name="ID0375" xml:space="preserve"> <data name="ID0375" xml:space="preserve">
<value>The protocol activation cannot be resolved or contains invalid data.</value> <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"> <data name="ID0434" xml:space="preserve">
<value>An error occurred while signing the user out.</value> <value>An error occurred while signing the user out.</value>
</data> </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"> <data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value> <value>The security token is missing.</value>
</data> </data>

12
src/OpenIddict.Client/OpenIddictClientService.cs

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

Loading…
Cancel
Save