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

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

Related

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.

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

Understanding ASP.net mvc4 code

public ActionResult Index()
{
return RedirectToAction("Index", "Login");
}
What does this code do? Index and login are views here?
This tells MVC to redirect to specified action instead of rendering HTML. In this case, browser receives the redirect notification and make a new request for the specified action. This acts like as Response.Redirect() in Asp.Net WebForm.
Moreover, RedirectToAction construct a redirect url to a specific action/controller in your application and use the route table to generate the correct URL.
For more about this look https://msdn.microsoft.com/en-us/library/system.web.mvc.controller.redirecttoaction(v=vs.118).aspx

Redirection to original URL having hash tag (#) broken in MVC4

I am developing an SPA application using AngularJS working with REST Web API, on top of a very small layer of ASP.NET MVC4. For reasons not important here, I am not using the default Account Controller of MVC4.
basically, I want to share "tasks" between users. My goal is to be able send the URL of a specific "task" entity to any user, via email. Clicking on that URL should launch the authentication. Following a successful authentication, I want to display the real task page info.
AngularJS causes my URLs to have # sign, or a URL of a page displaying the task "XYZ123" is:
http://hostname.com/#/tasks/XYZ123
ASP.NET redirects the unauthorized access to that URL to:
http://hostname.com/Home/Login?ReturnUrl=%2f#/tasks/XYZ123
This is OK, but the relevant controller method "cuts out" the path from #, so in:
public ActionResult Login(string returnUrl)
the value of 'returnUrl' will be just "/"
So, I am losing the path: I would like to build a "Connect with Facebook" link having the original URL, like:
http://hostname.com/Login/ExternalLogin?ReturnUrl=%2F#/tasks/XYZ123
but I cannot.
What is the right way to solve this issue?
I can think of creating my own redirection service URL without # tag, but this solution implies additional work, and covers only a case when the system is sending a message with task URL - humans will still try to copy/paste the location URL from the browser.
Thanks for any hint.
Max
Yes. A browser cuts '#/tasks/XYZ123' and requests page without that hash.
Although the hash itself apears on the logon page - it's the browser's work again.
Hash is not traveling to the server.
So when a browser loads the logon page with ?ReturnUrl=%2f#/tasks/XYZ123 we can rewrite Form action and encode the hash.
If the form looks like:
<form action="/Home/Login" method="post" >
...
</form>
The javascript code should look like:
<script src="~/js/jquery.js"></script>
<script type="text/javascript">
$(function() {
var search = $(location).attr('search') || '';
var hash = $(location).attr('hash') || '';
if (hash.length === 0) {
if (window.history.pushState) {
window.history.pushState('login', 'Login', '/Home/Login');
}
} else if (search === '?ReturnUrl=%2f') {
$('form').attr('action', '/Home/Login' + search + encodeURIComponent(hash) );
}
});
</script>
The part with window.history.pushState is required for the following:
If there is no hash, then for a SPA its URL (more likely) will be:
http://hostname.com/Home/Login?ReturnUrl=%2f
so here we try to replace URL (without page reload) with more accurate
http://hostname.com/Home/Login
You can use the properties of Request (like .Urlor .QueryString) to get the original url (and url parameters), instead of relying on the automatic binding of returnUrl parameter.
Replace # in the returnUrl with %23

Redirect After Registration in Drupal

Any one know how to go back to the "last page" after a user is presented the login screen and chooses to create a new account?
Basically the sequence is this:
User tries to get to protected content
Redirected to login page
If he logs in he is redirected to original page
If he chooses "create new account" and fills it out, he is redirected to the home page
How do we get him automatically redirected to the original page (not a static page).
There are several ways to go about this. The most straight-forward is to have a login link somewhere in the navigation that appends the destination to the url. The code for this is something like:
<?php
if (user_is_anonymous()) {
$link = l(t('Login'), 'user/login', array('query' => drupal_get_destination()));
}
?>
You can also set a custom access denied page at admin/settings/error-reporting that could either go to a callback that outputs the above code, or to a simple php node that outputs that code.
Additionally, the user login block provided with Drupal core uses the same method to redirect a successful login back to the originating page.
Edit: Note that the above methods will rarely work for registration, because there are more steps involved there. Specifically, when a user needs to verify an email address, passing the originating destination along via the email would involve modifying the core user registration process.
It will potentially still work on a site configured to not verify email addresses. The idea then would be to provide 2 links: 1 for login and the other for registration, both passing along destination information.
LoginToboggan may also be worth pursuing, although it doesn't yet offer the exact registration feature you're looking for.
straight php would be to include a header of this form:
<?php header("Location: " . $_SERVER['HTTP_REFERER']); ?>
for more information refer to the php manual
EDIT:
you can also include a hidden field in your form and set it to
$url = $_SERVER['PHP_SELF']; // or HTTP_REFERER depending on the setup
include the header code snipped to your registration form.