diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs index bdfbecc42..0eab91b44 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs @@ -10,7 +10,6 @@ using OpenIddict.Client; using OpenIddict.Client.Owin; using static OpenIddict.Abstractions.OpenIddictConstants; -using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; namespace OpenIddict.Sandbox.AspNet.Client.Controllers { @@ -22,20 +21,10 @@ public AuthenticationController(OpenIddictClientService service) => _service = service; [HttpPost, Route("~/login"), ValidateAntiForgeryToken] - public ActionResult LogIn(string provider, string returnUrl) + public async Task LogIn(string provider, string returnUrl) { var context = HttpContext.GetOwinContext(); - // Note: OpenIddict always validates the specified provider name when handling the challenge operation, - // but the provider can also be validated earlier to return an error page or a special HTTP error code. - if (!string.Equals(provider, "Local", StringComparison.Ordinal) && - !string.Equals(provider, "Local+GitHub", StringComparison.Ordinal) && - !string.Equals(provider, Providers.GitHub, StringComparison.Ordinal) && - !string.Equals(provider, Providers.Google, StringComparison.Ordinal)) - { - return new HttpStatusCodeResult(400); - } - // The local authorization server sample allows the client to select the external // identity provider that will be used to eventually authenticate the user. For that, // a custom "identity_provider" parameter is sent to the authorization server so that @@ -64,6 +53,14 @@ public ActionResult LogIn(string provider, string returnUrl) else { + // Note: OpenIddict always validates the specified provider name when handling the challenge operation, + // but the provider can also be validated earlier to return an error page or a special HTTP error code. + var registrations = await _service.GetClientRegistrationsAsync(); + if (!registrations.Any(registration => string.Equals(registration.ProviderName, provider, StringComparison.Ordinal))) + { + return new HttpStatusCodeResult(400); + } + var properties = new AuthenticationProperties(new Dictionary { // Note: when only one client is registered in the client options, diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/HomeController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/HomeController.cs index c58fe9835..fcdb7aa55 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/HomeController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/HomeController.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; @@ -8,6 +9,7 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using OpenIddict.Client; +using OpenIddict.Sandbox.AspNet.Client.ViewModels.Home; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Client.Owin.OpenIddictClientOwinConstants; @@ -27,11 +29,16 @@ public HomeController( } [HttpGet, Route("~/")] - public ActionResult Index() => View(); - - [Authorize, HttpPost, Route("~/message")] - [ValidateAntiForgeryToken] - public async Task Index(CancellationToken cancellationToken) + public async Task Index(CancellationToken cancellationToken) => View(new IndexViewModel + { + Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) + where !string.IsNullOrEmpty(registration.ProviderName) + where !string.IsNullOrEmpty(registration.ProviderDisplayName) + select registration + }); + + [Authorize, HttpPost, Route("~/message"), ValidateAntiForgeryToken] + public async Task GetMessage(CancellationToken cancellationToken) { var context = HttpContext.GetOwinContext(); @@ -46,7 +53,14 @@ public async Task Index(CancellationToken cancellationToken) using var response = await client.SendAsync(request, cancellationToken); response.EnsureSuccessStatusCode(); - return View(model: await response.Content.ReadAsStringAsync()); + return View("Index", new IndexViewModel + { + Message = await response.Content.ReadAsStringAsync(), + Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) + where !string.IsNullOrEmpty(registration.ProviderName) + where !string.IsNullOrEmpty(registration.ProviderDisplayName) + select registration + }); } [Authorize, HttpPost, Route("~/refresh-token")] @@ -82,7 +96,14 @@ public async Task RefreshToken(CancellationToken cancellationToken context.Authentication.SignIn(properties, ticket.Identity); - return View("Index", model: result.AccessToken); + return View("Index", new IndexViewModel + { + Message = result.AccessToken, + Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) + where !string.IsNullOrEmpty(registration.ProviderName) + where !string.IsNullOrEmpty(registration.ProviderDisplayName) + select registration + }); } } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs index fffe7f7fb..624e369cb 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs @@ -67,6 +67,7 @@ public void Configuration(IAppBuilder app) { Issuer = new Uri("https://localhost:44349/", UriKind.Absolute), ProviderName = "Local", + ProviderDisplayName = "Local OIDC server", ClientId = "mvc", ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/ViewModels/Home/IndexViewModel.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/ViewModels/Home/IndexViewModel.cs new file mode 100644 index 000000000..a1ec3404a --- /dev/null +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/ViewModels/Home/IndexViewModel.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Web.ModelBinding; +using OpenIddict.Client; + +namespace OpenIddict.Sandbox.AspNet.Client.ViewModels.Home; + +public class IndexViewModel +{ + [BindNever] + public string Message { get; set; } + + [BindNever] + public IEnumerable Providers { get; set; } +} diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml b/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml index 5eb4f5872..c1e00d400 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml @@ -4,7 +4,8 @@ @using Microsoft.Owin.Security.Cookies @using OpenIddict.Abstractions @using OpenIddict.Client.Owin -@model string +@using OpenIddict.Sandbox.AspNet.Client.ViewModels.Home; +@model IndexViewModel
@if (User?.Identity is { IsAuthenticated: true }) @@ -18,9 +19,9 @@ }

- if (!string.IsNullOrEmpty(Model)) + if (!string.IsNullOrEmpty(Model.Message)) { -

Payload returned by the controller: @Model

+

Payload returned by the controller: @Model.Message

} if (User is ClaimsPrincipal principal && principal.FindFirst(OpenIddictConstants.Claims.Private.ProviderName)?.Value is "Local") @@ -61,21 +62,16 @@ - - - - - + @foreach (var provider in Model.Providers) + { + + } }
\ No newline at end of file diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs index 8f81ecd9d..1ba37311a 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs @@ -5,7 +5,6 @@ using OpenIddict.Client; using OpenIddict.Client.AspNetCore; using static OpenIddict.Abstractions.OpenIddictConstants; -using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; namespace OpenIddict.Sandbox.AspNetCore.Client.Controllers; @@ -17,19 +16,8 @@ public AuthenticationController(OpenIddictClientService service) => _service = service; [HttpPost("~/login"), ValidateAntiForgeryToken] - public ActionResult LogIn(string provider, string returnUrl) + public async Task LogIn(string provider, string returnUrl) { - // Note: OpenIddict always validates the specified provider name when handling the challenge operation, - // but the provider can also be validated earlier to return an error page or a special HTTP error code. - if (!string.Equals(provider, "Local", StringComparison.Ordinal) && - !string.Equals(provider, "Local+GitHub", StringComparison.Ordinal) && - !string.Equals(provider, Providers.GitHub, StringComparison.Ordinal) && - !string.Equals(provider, Providers.Google, StringComparison.Ordinal) && - !string.Equals(provider, Providers.Reddit, StringComparison.Ordinal)) - { - return BadRequest(); - } - // The local authorization server sample allows the client to select the external // identity provider that will be used to eventually authenticate the user. For that, // a custom "identity_provider" parameter is sent to the authorization server so that @@ -58,6 +46,14 @@ public ActionResult LogIn(string provider, string returnUrl) else { + // Note: OpenIddict always validates the specified provider name when handling the challenge operation, + // but the provider can also be validated earlier to return an error page or a special HTTP error code. + var registrations = await _service.GetClientRegistrationsAsync(); + if (!registrations.Any(registration => string.Equals(registration.ProviderName, provider, StringComparison.Ordinal))) + { + return BadRequest(); + } + var properties = new AuthenticationProperties(new Dictionary { // Note: when only one client is registered in the client options, diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/HomeController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/HomeController.cs index 767416776..40be2ae2a 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/HomeController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/HomeController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using OpenIddict.Client; +using OpenIddict.Sandbox.AspNetCore.Client.ViewModels.Home; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Client.AspNetCore.OpenIddictClientAspNetCoreConstants; @@ -23,7 +24,13 @@ public HomeController( } [HttpGet("~/")] - public ActionResult Index() => View(); + public async Task Index(CancellationToken cancellationToken) => View(new IndexViewModel + { + Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) + where !string.IsNullOrEmpty(registration.ProviderName) + where !string.IsNullOrEmpty(registration.ProviderDisplayName) + select registration + }); [Authorize, HttpPost("~/message"), ValidateAntiForgeryToken] public async Task GetMessage(CancellationToken cancellationToken) @@ -40,7 +47,14 @@ public async Task GetMessage(CancellationToken cancellationToken) using var response = await client.SendAsync(request, cancellationToken); response.EnsureSuccessStatusCode(); - return View("Index", model: await response.Content.ReadAsStringAsync(cancellationToken)); + return View("Index", new IndexViewModel + { + Message = await response.Content.ReadAsStringAsync(cancellationToken), + Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) + where !string.IsNullOrEmpty(registration.ProviderName) + where !string.IsNullOrEmpty(registration.ProviderDisplayName) + select registration + }); } [Authorize, HttpPost("~/refresh-token"), ValidateAntiForgeryToken] @@ -78,6 +92,13 @@ public async Task RefreshToken(CancellationToken cancellationToken // authentication options shouldn't be used, a specific scheme can be specified here. await HttpContext.SignInAsync(ticket.Principal, properties); - return View("Index", model: result.AccessToken); + return View("Index", new IndexViewModel + { + Message = result.AccessToken, + Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) + where !string.IsNullOrEmpty(registration.ProviderName) + where !string.IsNullOrEmpty(registration.ProviderDisplayName) + select registration + }); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs index 37b56454a..fd27fe3e6 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs @@ -103,6 +103,7 @@ public void ConfigureServices(IServiceCollection services) { Issuer = new Uri("https://localhost:44395/", UriKind.Absolute), ProviderName = "Local", + ProviderDisplayName = "Local OIDC server", ClientId = "mvc", Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" }, diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/ViewModels/Home/IndexViewModel.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/ViewModels/Home/IndexViewModel.cs new file mode 100644 index 000000000..c929f0a5e --- /dev/null +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/ViewModels/Home/IndexViewModel.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding; +using OpenIddict.Client; + +namespace OpenIddict.Sandbox.AspNetCore.Client.ViewModels.Home; + +public class IndexViewModel +{ + [BindNever] + public string Message { get; set; } + + [BindNever] + public IEnumerable Providers { get; set; } +} diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml index b60645d00..979dfead3 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml @@ -1,8 +1,9 @@ @using System.Security.Claims @using Microsoft.AspNetCore.Authentication; @using OpenIddict.Client.AspNetCore; +@using OpenIddict.Sandbox.AspNetCore.Client.ViewModels.Home; @using static OpenIddict.Abstractions.OpenIddictConstants; -@model string +@model IndexViewModel
@if (User?.Identity is { IsAuthenticated: true }) @@ -16,9 +17,9 @@ }

- if (!string.IsNullOrEmpty(Model)) + if (!string.IsNullOrEmpty(Model.Message)) { -

Payload returned by the controller: @Model

+

Payload returned by the controller: @Model.Message

} if (User.FindFirst(Claims.Private.ProviderName)?.Value is "Local") @@ -47,25 +48,16 @@
- - - - - - - + @foreach (var provider in Model.Providers) + { + + }
}
\ No newline at end of file diff --git a/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs b/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs index fbf50ddab..0a844a3a2 100644 --- a/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs +++ b/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs @@ -4,7 +4,6 @@ using Spectre.Console; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Abstractions.OpenIddictExceptions; -using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; #if !SUPPORTS_HOST_APPLICATION_LIFETIME using IHostApplicationLifetime = Microsoft.Extensions.Hosting.IApplicationLifetime; @@ -143,13 +142,17 @@ static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt( return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken); } - static Task GetSelectedProviderAsync(CancellationToken cancellationToken) + Task GetSelectedProviderAsync(CancellationToken cancellationToken) { - static string Prompt() => AnsiConsole.Prompt(new SelectionPrompt() + async Task PromptAsync() => AnsiConsole.Prompt(new SelectionPrompt() .Title("Select the authentication provider you'd like to log in with.") - .AddChoices("Local", Providers.GitHub, Providers.Twitter)); + .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(Prompt, cancellationToken), cancellationToken); + return WaitAsync(Task.Run(PromptAsync, cancellationToken), cancellationToken); } static async Task WaitAsync(Task task, CancellationToken cancellationToken) diff --git a/sandbox/OpenIddict.Sandbox.Console.Client/Program.cs b/sandbox/OpenIddict.Sandbox.Console.Client/Program.cs index f4fd810e4..099c67c17 100644 --- a/sandbox/OpenIddict.Sandbox.Console.Client/Program.cs +++ b/sandbox/OpenIddict.Sandbox.Console.Client/Program.cs @@ -68,6 +68,7 @@ { Issuer = new Uri("https://localhost:44395/", UriKind.Absolute), ProviderName = "Local", + ProviderDisplayName = "Local authorization server", ClientId = "console", RedirectUri = new Uri("callback/login/local", UriKind.Relative), diff --git a/src/OpenIddict.Client/OpenIddictClientService.cs b/src/OpenIddict.Client/OpenIddictClientService.cs index 27112616a..2ffdfb187 100644 --- a/src/OpenIddict.Client/OpenIddictClientService.cs +++ b/src/OpenIddict.Client/OpenIddictClientService.cs @@ -4,6 +4,7 @@ * the license and the contributors participating to this project. */ +using System.Collections.Immutable; using System.Diagnostics; using System.Security.Claims; using Microsoft.Extensions.DependencyInjection; @@ -30,6 +31,27 @@ public class OpenIddictClientService public OpenIddictClientService(IServiceProvider provider) => _provider = provider ?? throw new ArgumentNullException(nameof(provider)); + /// + /// Gets all the client registrations that were registered in the client options. + /// + /// The that can be used to abort the operation. + /// The client registrations that were registered in the client options. + public virtual ValueTask> GetClientRegistrationsAsync( + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new(Task.FromCanceled>(cancellationToken)); + } + + var options = _provider.GetRequiredService>(); + return new(options.CurrentValue.Registrations switch + { + [ ] => ImmutableArray.Create(), + [..] registrations => registrations.ToImmutableArray() + }); + } + /// /// Resolves the client registration associated with the specified issuer . ///