Customize Identity Server 4 Login UI for Hosted Blazor WebAssembly - asp.net-core

I have a solution based on the Visual Studio template that is successfully using IdentityServer4.
What think I understand is:
The IS4 integration implements endpoints at /authentication/{action}, e.g. authentication/login
IS4 routes that request to host/Identity/Account/Login.cshtml. I have scaffolded identity so I can see that .cshtml file in my project.
The code-behind Login.cshtml.cs takes care of speaking with SignInManager, etc.
In the client project the "LoginDisplay.razor" component redirects to /authentication/login when I click the login button. I presume that this is where step 1 above is invoked, which redirects to the location in step 2.
Fine. Now, I want to customise the whole login UI.
From instructions at: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-5.0 I can see that:
I can configure authentication paths to be anything I want. For example:
builder.Services.AddApiAuthorization(options => {
options.AuthenticationPaths.LogInPath = "security/login";
})
So, I have created a razor component to handle security/login:
#page "/security/{action}"
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="#Action">
<LoggingIn>
This is the new login page
</LoggingIn>
</RemoteAuthenticatorView>
#code{
[Parameter] public string Action { get; set; }
}
My expectation was that after clicking "Login", I would be routed to the above and presented with a page that just said:
"This is the new login page"
and thought that from there, I would:
Customise the UI within the <LoggingIn> fragment.
Make a call to an API that would then replicate the logic in the scaffolded login.cshtml file that actually logs the user in.
That line of code from the login.cshtml.cs file looks like this:
var result = await _signInManager.PasswordSignInAsync(
Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
However, it seems that the razor component I created for /security/login is simply a 'transient' message that appears before being routed to the scaffolded login.csthml file.
So, to me, it seems that I am unable to actually change the physical page used to collect the user's credentials. I am only able to change the transient screen that appears before the originally scaffolded login page is shown.
Does this mean that if I want a customised UI for logging in I need to directly edit the scaffolded page as opposed to creating a whole new UI in the WebAssembly project that calls an APIController that I create to take care of using SignInManager?
It would actually be a lot less work to do that than to take the approach of creating a Client-side UI that calls an API that I create, etc. so, with hindsight, editing the scaffolded cshtml file is probably the best way to go. But am still confused as to what value is really being brought by being able to configure options.AuthenticationPaths.LogInPath.

Related

How to show the .Net Core Identity RazorPage somewhere other than its default location?

.Net Core 6, MVC, and your basic authentication with Identity set up (which is RazorPages only), but I want my Home/Index.cshtml page to "host" the Identity/Account/Login page, preferably without having to resort to an iFrame.
Everything I've seen so far is simply how to build the Login page, but nothing on how to put the page somewhere besides its default location.
How do I put the Login form/page on my MVC Home/Index.cshtml page?
How do I put the Login form/page on my MVC Home/Index.cshtml page
Login page is a razor page not a partial view, so you cannot use <partial> to add Login page in the Index view. You can put your code for login in the Index.cshtml, but you may need change a lot, like building the Login page.
If you add below code in the _layout, so that you can login at any view you want.
<partial name="_LoginPartial" />
result:

How to Specify the "Authorize" at the Area level with Razor Pages?

I am testing the WebApp authentication using simple cookie. The app has 2 areas: Public & Internal (see Area structure at the end of this post). Users are required to login when accessing all pages under the "Internal" area.
PROGRAM DESCRIPTION
The App is implemented with .NET 6 with Razor pages (without MVC). In the program.cs file, I tried to specify the "authorize" at the "Area" level for "Internal" area (see code segment here). The HTML 404.15 error occurred when testing the app (see HTML error in attached image below).
It looks like that "AuthorizeAreaFolder" works fine with any folder name inside of Internal area. Is there a way to setup "Authorize" at an Area level?
CONFIGURE AUTHORIZE
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizeAreaFolder("Internal","/");
});
HTML ERROR
WEBAPP AREAS
Make sure that your signing in page is accessible to anonymous users. An easy way to accomplish this is to add the AllowAnonymous attribute to the PageModel class of the relevant page e.g (assuming the signing in page is called Signin):
[AllowAnonymous]
public class SigninModel : PageModel

Azure B2C: how to Include domain hint in Sign Up/In links from asp.net core control

I'm using Azure B2C, I would like to use Google to sign in users. I would like to do something like this in my asp.net core page to achieve it:
<a asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn" asp-route-domainHint="google.com">Google Sign Up/In</a>
So that the Google challenge page is used to sign in the user.
However, this code results in the non-social ("organic") sign in page being used, instead, because the domain_hint parameter is not included in the auth redirect.
Looking at the account controller code, here: https://github.com/AzureAD/microsoft-identity-web/blob/master/src/Microsoft.Identity.Web.UI/Areas/MicrosoftIdentity/Controllers/AccountController.cs
The only way to pass a domain hint to the Account controller, is by using the Challenge action.
<a asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="Challenge" asp-route-domainHint="google.com">Google Sign Up/In</a>
This almost works.
But it loops back again to Google challenge page, after you choose your google account, repeatedly.
Is there a way to pass a domain hint and use the SignIn action of the Account controller? Or some other approach for generating Sign In calls from an asp.net core page and include a domain hint?
I have seen this answer, but would prefer to not build up the entire href address from scratch: Azure B2C with domain hint instead of IdP buttons
Thanks very much
To redirect users to an external identity provider, do the following:
Check the domain name of your external identity provider. For more information, see Redirect sign-in to a social provider.
Complete the Support advanced scenarios procedure.
In the OnRedirectToIdentityProviderFunc function, add the following line of code to the OnRedirectToIdentityProvider function:
private async Task OnRedirectToIdentityProviderFunc(RedirectContext context)
{
context.ProtocolMessage.DomainHint = "facebook.com";
// More code
await Task.CompletedTask.ConfigureAwait(false);
}
https://learn.microsoft.com/en-us/azure/active-directory-b2c/enable-authentication-web-application-options#preselect-an-identity-provider
Jas Suri's answer helped quite a bit, but there are a few code-related steps that I did to complete the solution:
To get that domain_hint parameter into the authentication link, you add some code to the ConfigureServices() method of the Startup class of your ASP.NET Core project:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAdB2C", options); // Assuming an appsettings.json section named AzureAdB2C with config data
options.Events ??= new OpenIdConnectEvents();
options.Events.OnRedirectToIdentityProvider += OnRedirectToIdentityProviderFunc;
});
The key part was subscribing to the OnRedirectToIdentityProvider event. Add an event handler for it in Startup.cs, which could look like this:
private async Task OnRedirectToIdentityProviderFunc(RedirectContext context)
{
var domainHint = context.HttpContext.Request.Query["domainHint"];
context.ProtocolMessage.DomainHint = domainHint.Count > 0 ? domainHint[0] : null;
await Task.CompletedTask.ConfigureAwait(false);
}
The middle line above gets the domainHint from the query string. So, you must set its value in your razor view as follows. Take note of the asp-route-domainHint attribute, which ASP.NET Core adds to the rendered Href as a query string value.
<a asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn" asp-route-domainHint="google.com">Google Sign Up/In</a>
In the final rendered HTML, this link becomes (note the domainHint querystring parameter):
Google Sign Up/In
When a user clicks on that link, the Account controller in Microsoft.Identity.Web will redirect the user to the authentication link with Microsoft (note the domain_hint parameter near the end) which results in the social auth provider's screen being full-screen:
https://your-ADB2C-intance/your-ADB2C-domain/b2c_1a_signup_signin/oauth2/v2.0/authorize?client_id=..etc..etc....&domain_hint=google.com&state=..etc..etc..

Blazor Webassembly Authenticated Event

When authenticating users with Microsoft.AspNetCore.Components.WebAssembly.Authentication, in a Blazor Webassembly application on the client side app that is hosted on ASP.NET Core (latest, blazor), when/how to call an action every time a user is authenticated? Even if they come back to the app and have an active session.
Update 2020-07-15:
The problem I am trying to overcome is that we have a service that goes to Graph API once a user logs in (MSAL) to get their info and profile image. After they log in, I use the RemoteAuthenticatorView.OnLogInSucceeded event to know when they actually logged in. Trouble is, if they then refresh the page (F5), the service loses the Graph info. I guess what I'm actually trying to achieve is to persist session data.
To overcome this, I so far settled with the workaround to use the Authorized section in razor of the LoginDisplay component, to trigger the service to check if the service user info is empty and then go to Graph again instead of trying to store in in localstorage or something like that... Then, I have user info displaying components subscribe to an event in the service to know when the user info needed updated to call StateHasChanged().
If there is a better solution to persisting service data across page refreshes, I am all eyes.
One way to solve this is to make a component that wraps your application, and check if the user is authenticated in the OnInitialized(). That, combined with subscribing to AuthenticationStateProvider.AuthenticationStateChanged worked for me. I posted a more detailed solution here.
I'm totally sure what you mean by "every time they are authenticated".
If you want to take an action on EVERY event (like a page load) then you could do it by embedding an AuthorizedView in your page:
<AuthorizeView>
<Authorized>
... do action for authorized users ...
</Authorized>
<NotAuthorized>
... do action for un-authorized users ...
</NotAuthorized>
</AuthorizeView>
If you want to trigger an action whenever the Authentication action is taken you can do it in OnParametersSet and looking at the Action to see what type of authentication event it is.
also see: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-3.1#customize-the-authentication-user-interface
It may be better to create custom RenderFragments for the specific Actions - it will depend on what you want to do I guess.
#page "/authentication/{action}"
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
#using Microsoft.AspNetCore.Components.WebAssembly.Hosting
#using System.Text.Json
#using System.Security.Claims
<RemoteAuthenticatorView Action="#Action" />
#inject ILogger<Authentication> logger
#code{
[Parameter]
public string Action { get; set; }
protected override void OnParametersSet()
{
base.OnParametersSet();
logger.LogInformation($"Action: {Action}");
//
// DO YOUR ACTION HERE
//
}
}

Adding user registration to IdentityServer4

I am using IdentityServer template is4aspid, which comes with asp.net identity, I would like to add a register page to it. I don't want to do it on my main MVC app as I don't want it to have access to the users DB, and hence the identityServer already has that reference, I thought it would be better to just add the registration step to it.
I copied the Register.cshtml page from another asp.net webapplication, where I scafolded the identity, but when I click on register, nothing happens, not sure what is happening
.
As you can see in the picture above, after clicking on the Register link, the url changes to the one supposed to provide the registration form, but the page remains the same of the login form.
I added the following line to _Layout.cshtml