Skip to content

Commit

Permalink
Introduce a new API returning the client registrations and update the…
Browse files Browse the repository at this point in the history
… samples to build the list of providers dynamically
  • Loading branch information
kevinchalet committed Dec 26, 2023
1 parent 60de5f2 commit 05ba317
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -22,20 +21,10 @@ public AuthenticationController(OpenIddictClientService service)
=> _service = service;

[HttpPost, Route("~/login"), ValidateAntiForgeryToken]
public ActionResult LogIn(string provider, string returnUrl)
public async Task<ActionResult> 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
Expand Down Expand Up @@ -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<string, string>
{
// Note: when only one client is registered in the client options,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
Expand All @@ -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;

Expand All @@ -27,11 +29,16 @@ public HomeController(
}

[HttpGet, Route("~/")]
public ActionResult Index() => View();

[Authorize, HttpPost, Route("~/message")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Index(CancellationToken cancellationToken)
public async Task<ActionResult> 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<ActionResult> GetMessage(CancellationToken cancellationToken)
{
var context = HttpContext.GetOwinContext();

Expand All @@ -46,7 +53,14 @@ public async Task<ActionResult> 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")]
Expand Down Expand Up @@ -82,7 +96,14 @@ public async Task<ActionResult> 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
});
}
}
}
1 change: 1 addition & 0 deletions sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<OpenIddictClientRegistration> Providers { get; set; }
}
26 changes: 11 additions & 15 deletions sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -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

<div class="jumbotron">
@if (User?.Identity is { IsAuthenticated: true })
Expand All @@ -18,9 +19,9 @@
}
</p>

if (!string.IsNullOrEmpty(Model))
if (!string.IsNullOrEmpty(Model.Message))
{
<h3>Payload returned by the controller: @Model</h3>
<h3>Payload returned by the controller: @Model.Message</h3>
}

if (User is ClaimsPrincipal principal && principal.FindFirst(OpenIddictConstants.Claims.Private.ProviderName)?.Value is "Local")
Expand Down Expand Up @@ -61,21 +62,16 @@

<input type="hidden" name="returnUrl" value="@Request.RawUrl" />

<button class="btn btn-lg btn-success" type="submit" name="provider" value="Local">
Sign in using the local OIDC server
</button>

<button class="btn btn-lg btn-success" type="submit" name="provider" value="Local+GitHub">
Sign in using the local OIDC server (preferred service: GitHub)
</button>

<button class="btn btn-lg btn-success" type="submit" name="provider" value="GitHub">
Sign in using GitHub
Sign in using Local OIDC server (preferred service: GitHub)
</button>

<button class="btn btn-lg btn-success" type="submit" name="provider" value="Google">
Sign in using Google
</button>
@foreach (var provider in Model.Providers)
{
<button class="btn btn-lg btn-success" type="submit" name="provider" value="@provider.ProviderName">
Sign in using @provider.ProviderDisplayName
</button>
}
</form>
}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -17,19 +16,8 @@ public AuthenticationController(OpenIddictClientService service)
=> _service = service;

[HttpPost("~/login"), ValidateAntiForgeryToken]
public ActionResult LogIn(string provider, string returnUrl)
public async Task<ActionResult> 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
Expand Down Expand Up @@ -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<string, string>
{
// Note: when only one client is registered in the client options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -23,7 +24,13 @@ public HomeController(
}

[HttpGet("~/")]
public ActionResult Index() => View();
public async Task<ActionResult> 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<ActionResult> GetMessage(CancellationToken cancellationToken)
Expand All @@ -40,7 +47,14 @@ public async Task<ActionResult> 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]
Expand Down Expand Up @@ -78,6 +92,13 @@ public async Task<ActionResult> 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
});
}
}
1 change: 1 addition & 0 deletions sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
Original file line number Diff line number Diff line change
@@ -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<OpenIddictClientRegistration> Providers { get; set; }
}
Original file line number Diff line number Diff line change
@@ -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

<div class="jumbotron">
@if (User?.Identity is { IsAuthenticated: true })
Expand All @@ -16,9 +17,9 @@
}
</p>

if (!string.IsNullOrEmpty(Model))
if (!string.IsNullOrEmpty(Model.Message))
{
<h3>Payload returned by the controller: @Model</h3>
<h3>Payload returned by the controller: @Model.Message</h3>
}

if (User.FindFirst(Claims.Private.ProviderName)?.Value is "Local")
Expand Down Expand Up @@ -47,25 +48,16 @@
<form asp-action="Login" asp-controller="Authentication" method="post">
<input type="hidden" name="returnUrl" value="@(Context.Request.PathBase + Context.Request.Path + Context.Request.QueryString)" />

<button class="btn btn-lg btn-success" type="submit" name="provider" value="Local">
Sign in using the local OIDC server
</button>

<button class="btn btn-lg btn-success" type="submit" name="provider" value="Local+GitHub">
Sign in using the local OIDC server (preferred service: GitHub)
Sign in using Local OIDC server (preferred service: GitHub)
</button>

<button class="btn btn-lg btn-success" type="submit" name="provider" value="GitHub">
Sign in using GitHub
</button>

<button class="btn btn-lg btn-success" type="submit" name="provider" value="Google">
Sign in using Google
</button>

<button class="btn btn-lg btn-success" type="submit" name="provider" value="Reddit">
Sign in using Reddit
</button>
@foreach (var provider in Model.Providers)
{
<button class="btn btn-lg btn-success" type="submit" name="provider" value="@provider.ProviderName">
Sign in using @provider.ProviderDisplayName
</button>
}
</form>
}
</div>
13 changes: 8 additions & 5 deletions sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -143,13 +142,17 @@ static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt(
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
}

static Task<string> GetSelectedProviderAsync(CancellationToken cancellationToken)
Task<string> GetSelectedProviderAsync(CancellationToken cancellationToken)
{
static string Prompt() => AnsiConsole.Prompt(new SelectionPrompt<string>()
async Task<string> PromptAsync() => AnsiConsole.Prompt(new SelectionPrompt<OpenIddictClientRegistration>()
.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<T> WaitAsync<T>(Task<T> task, CancellationToken cancellationToken)
Expand Down
Loading

0 comments on commit 05ba317

Please sign in to comment.