Blazor Webassembly Authenticated Event - asp.net-core

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
//
}
}

Related

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..

Customize Identity Server 4 Login UI for Hosted Blazor WebAssembly

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.

Vue changing the component without URL changes

I'm in Registration.vue component. The component contains registration form with email, password, etc.. fields.
I would like to thanks user for successful registering (with instruction that he should go check email).
What is the best solution to do this?
I was thinking about:
redirecting to second component using this.$router.push or this.$router.replace but this will change URL and when somebody go this URL without registering he will see message that he should check email...
replacing current component with other when registering action successful but I dont know how to do this (without URL change, and with good code).
using <component v-bind:is="currentView"> but I am not sure if this is best solution. I need to make three files (for parent component with :is, for form and for thanks). Also i need to emit event from child that registration went well, but on the other hand i should fire vuex registration action and dont expect for response (see the next sequence)
The other thing is that we should not wait for the vuex action to be completed, but i need to know if registration went well - https://github.com/vuejs/vuex/issues/46#issuecomment-174539828
I am using vue.js 2, vue-router, vuex
Thanks
You could use something like Sweet Alert to display a success or error dialog. It supports Ajax requests so you can display a "your registration is processing, please check your email" message while it is being handled.
The first approach is suitable when user successfully registers and then redirected to login page.
Now issue how to check whether user has entered required field? So there comes form validations. You can use vee-validate plugin and it's perfect for all projects. I am using it and it has so many available validations.
With these UI validations, after they are passed successfully then only submit action will be fired or else user will be prompted for entering required field.
You can see basic example here - http://vee-validate.logaretm.com/index.html#basic-example
When action is performed,
///main.js
...
Vue.use(VeeValidate)
...
// register.vue
this.$validator.validateAll().then((result) => {
if (result) {
//done
this.$router.replace( '/login' );
}
else{
// throw error
}
Simple as that.
Try this approach if you want the UI validations on the form.

Calling a controller method (from an external email link) that does not return a View, but simply exits

I have an MVC 4 Razor intranet app using Windows credentials where a new user must register (existing users are automatically redirected to the main user page). This generates an approval request email to the admin and returns a view with an Exit button simply saying your application has been submitted. The user then closes the app.
The email is in HTML and presents the user's entered data and has Approved or Declined selections. It then has a button to invoke code to insert the user into the Members table (if accepted) and return an email to the user with the decision.
Since some time may transpire between the app mailing the admin and the admin making the decision, the session will originally terminate. I need to have the email invoke a method to persist the new user, email the decision, and then simply exit. It will not involve the browser in any way, so no Views are involved. I could write this as a background console app, but that would involve duplicating a lot of code in the MVC app - with referential integrity issues on bug fixes or updates.
How can I write a method in the Controller that can be invoked by a link in the email as if from a browser that does the work and then exits without returning anything to a browser?
If I make the method an ActionResult method and return new EmptyResult() or return(null) I assume it will try to return an empty page to nowhere.
Can I alternatively construct a method in my Controller, where it has access to all of the support code, like?
public void EnrollMember(Member member, bool decision)
{
if (decision == true)
{
// insert new user into Members table
// generate accepted email
}
else
{
// generate declined email
}
}
and then just link to it the same way I link to Index(), /MyController/EnrollMember(...)?
There is no main() in the app since it is an ASP.NET MVC app, and I don't know how to terminate the app from within a Controller method without trying to return something, instead of from a View in the browser.
I'm a long-time programmer, but a .NET newbie so this probably has a simple answer.
The "view" is just the response. You don't necessarily have to return a view, but you must return a response. There's no way around that. If the link opens in a browser, then something will be displayed, regardless. Even if you were to just return an empty ContentResult, at least a blank page will be displayed in the browser.
To achieve something akin to what you're looking for, your best bet would be to return an HTML document with just a simple bit of JavaScript that will act to close the window:
<html>
<body>
<script>
window.close();
</script>
</body>
</html>

symfony 2 how to display the error after login with custom authentication handler

I have created an authentication handler and everything is working fine when the user authenticates with success...but when there is a failure I don't know how to return the failure error to my form and display it.
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
}
How should my function look like because I do not know how to pass $exception and redirect back to my form.
You probably want to display a message to the user.
You can display a flash message and redirect the user.
$request->getSession()->setFlash('error', $exception->getMessage());
If you look at the FosUserBundle they display a message direcly in the template
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/views/Security/login.html.twig
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Controller/SecurityController.php
Hope it's helpful
Best regard