diff --git a/samples/sample-blazor/Program.cs b/samples/sample-blazor/Program.cs index 014eb11..6022c9a 100644 --- a/samples/sample-blazor/Program.cs +++ b/samples/sample-blazor/Program.cs @@ -47,8 +47,35 @@ { if (!(context.User?.Identity?.IsAuthenticated ?? false)) { - await context.ChallengeAsync(new AuthenticationProperties { RedirectUri = "/" }); - } else { + var authProperties = new AuthenticationProperties + { + RedirectUri = "/" + }; + + /// + /// + authProperties.SetParameter("first_screen", LogtoParameters.Authentication.FirstScreen.Register); + + // This parameter MUST be used together with `first_screen`. + authProperties.SetParameter("identifiers", string.Join(",", new[] + { + LogtoParameters.Authentication.Identifiers.Username, + })); + + var directSignIn = new LogtoParameters.Authentication.DirectSignIn + { + Target = "github", + Method = LogtoParameters.Authentication.DirectSignIn.Methods.Social + }; + + /// + /// + authProperties.SetParameter("direct_sign_in", System.Text.Json.JsonSerializer.Serialize(directSignIn)); + + await context.ChallengeAsync(authProperties); + } + else + { context.Response.Redirect("/"); } }); diff --git a/samples/sample-mvc/Controllers/HomeController.cs b/samples/sample-mvc/Controllers/HomeController.cs index b1dfa96..b0c813d 100644 --- a/samples/sample-mvc/Controllers/HomeController.cs +++ b/samples/sample-mvc/Controllers/HomeController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using sample_mvc.Models; +using System.Text.Json; namespace sample_mvc.Controllers; @@ -25,7 +26,32 @@ public async Task Index() public IActionResult SignIn() { - return Challenge(new AuthenticationProperties { RedirectUri = "/" }); + var authProperties = new AuthenticationProperties + { + RedirectUri = "/" + }; + + /// + /// + authProperties.SetParameter("first_screen", LogtoParameters.Authentication.FirstScreen.Register); + + // This parameter MUST be used together with `first_screen`. + authProperties.SetParameter("identifiers", string.Join(",", new[] + { + LogtoParameters.Authentication.Identifiers.Username, + })); + + var directSignIn = new LogtoParameters.Authentication.DirectSignIn + { + Target = "github", + Method = LogtoParameters.Authentication.DirectSignIn.Methods.Social + }; + + /// + /// + authProperties.SetParameter("direct_sign_in", JsonSerializer.Serialize(directSignIn)); + + return Challenge(authProperties); } // Use the `new` keyword to avoid conflict with the `ControllerBase.SignOut` method diff --git a/samples/sample/Pages/Index.cshtml.cs b/samples/sample/Pages/Index.cshtml.cs index ec836dc..1a94fc1 100644 --- a/samples/sample/Pages/Index.cshtml.cs +++ b/samples/sample/Pages/Index.cshtml.cs @@ -1,6 +1,7 @@ using Logto.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Text.Json; namespace sample.Pages; @@ -22,7 +23,32 @@ public async Task OnGetAsync() public async Task OnPostSignInAsync() { - await HttpContext.ChallengeAsync(new AuthenticationProperties { RedirectUri = "/" }); + var authProperties = new AuthenticationProperties + { + RedirectUri = "/" + }; + + /// + /// + authProperties.SetParameter("first_screen", LogtoParameters.Authentication.FirstScreen.Register); + + // This parameter MUST be used together with `first_screen` + authProperties.SetParameter("identifiers", string.Join(",", new[] + { + LogtoParameters.Authentication.Identifiers.Username, + })); + + var directSignIn = new LogtoParameters.Authentication.DirectSignIn + { + Target = "github", + Method = LogtoParameters.Authentication.DirectSignIn.Methods.Social + }; + + /// + /// + authProperties.SetParameter("direct_sign_in", JsonSerializer.Serialize(directSignIn)); + + await HttpContext.ChallengeAsync(authProperties); } public async Task OnPostSignOutAsync() diff --git a/src/Logto.AspNetCore.Authentication/LogtoParameters.cs b/src/Logto.AspNetCore.Authentication/LogtoParameters.cs index 86a4cca..58b62b2 100644 --- a/src/Logto.AspNetCore.Authentication/LogtoParameters.cs +++ b/src/Logto.AspNetCore.Authentication/LogtoParameters.cs @@ -1,4 +1,5 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using System.Collections.Generic; namespace Logto.AspNetCore.Authentication; @@ -115,4 +116,96 @@ public static class Claims /// public const string Identities = "identities"; } + + /// + /// The authentication parameters for Logto sign-in experience customization. + /// + public static class Authentication + { + /// + /// The first screen to show in the sign-in experience. + /// See for more details. + /// + public static class FirstScreen + { + /// + /// Show the register form first. + /// + public const string Register = "identifier:register"; + + /// + /// Show the sign-in form first. + /// + public const string SignIn = "identifier:sign_in"; + + /// + /// Show the single sign-on form first. + /// + public const string SingleSignOn = "single_sign_on"; + + /// + /// Show the reset password form first. + /// + public const string ResetPassword = "reset_password"; + } + + /// + /// The identifiers to use for authentication. + /// This parameter MUST be used together with . + /// + public static class Identifiers + { + /// + /// Use email for authentication. + /// + public const string Email = "email"; + + /// + /// Use phone for authentication. + /// + public const string Phone = "phone"; + + /// + /// Use username for authentication. + /// + public const string Username = "username"; + } + + /// + /// Direct sign-in configuration. + /// See for more details. + /// + public class DirectSignIn + { + /// + /// The target identifier for direct sign-in. + /// + public string Target { get; set; } = string.Empty; + + /// + /// The sign-in method. + /// + public string Method { get; set; } = string.Empty; + + public static class Methods + { + /// + /// Single sign-on method. + /// + public const string Sso = "sso"; + + /// + /// Social sign-in method. + /// + public const string Social = "social"; + } + } + + /// + /// Extra parameters to be passed to the authorization endpoint. + /// + public class ExtraParams : Dictionary + { + } + } } diff --git a/src/Logto.AspNetCore.Authentication/extensions/AuthenticationBuilderExtensions.cs b/src/Logto.AspNetCore.Authentication/extensions/AuthenticationBuilderExtensions.cs index 4d4485f..2688a3d 100644 --- a/src/Logto.AspNetCore.Authentication/extensions/AuthenticationBuilderExtensions.cs +++ b/src/Logto.AspNetCore.Authentication/extensions/AuthenticationBuilderExtensions.cs @@ -9,6 +9,7 @@ namespace Logto.AspNetCore.Authentication; using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; +using System.Threading.Tasks; /// /// Extension methods to configure Logto authentication. @@ -101,11 +102,49 @@ private static void ConfigureOpenIdConnectOptions(OpenIdConnectOptions options, options.ClaimActions.MapAllExcept("nbf", "nonce", "c_hash", "at_hash"); options.Events = new OpenIdConnectEvents { + OnRedirectToIdentityProvider = context => + { + if (context.Properties.Parameters.TryGetValue("first_screen", out var firstScreen)) + { + context.ProtocolMessage.Parameters.Add("first_screen", firstScreen?.ToString()); + } + + if (context.Properties.Parameters.TryGetValue("identifiers", out var identifiers)) + { + context.ProtocolMessage.Parameters.Add("identifiers", identifiers?.ToString()); + } + + if (context.Properties.Parameters.TryGetValue("direct_sign_in", out var directSignIn)) + { + var directSignInOption = System.Text.Json.JsonSerializer.Deserialize( + directSignIn?.ToString() ?? "{}" + ); + if (directSignInOption != null && !string.IsNullOrEmpty(directSignInOption.Method) && !string.IsNullOrEmpty(directSignInOption.Target)) + { + context.ProtocolMessage.Parameters.Add("direct_sign_in", $"{directSignInOption.Method}:{directSignInOption.Target}"); + } + } + + if (context.Properties.Parameters.TryGetValue("extra_params", out var extraParams)) + { + var parameters = System.Text.Json.JsonSerializer.Deserialize( + extraParams?.ToString() ?? "{}" + ); + if (parameters != null) + { + foreach (var param in parameters) + { + context.ProtocolMessage.Parameters.Add(param.Key, param.Value); + } + } + } + + return Task.CompletedTask; + }, OnRedirectToIdentityProviderForSignOut = async context => { // Clean up the cookie when signing out. await context.HttpContext.SignOutAsync(cookieScheme); - // Rebuild parameters since we use client_id for sign-out, no need to use id_token_hint. context.ProtocolMessage.Parameters.Remove(OpenIdConnectParameterNames.IdTokenHint); context.ProtocolMessage.Parameters.Add(OpenIdConnectParameterNames.ClientId, logtoOptions.AppId);