8
votes

Problem

I am trying to handle the 'Reset password' user-flow in my application. Upon clicking the 'Forgot Password' link, the OnRemoteFailure OpenId event is successfully triggered, successfully redirecting to the specified url 'Home/ResetPassword', but instead or redirecting to the ADB2C reset password screen, it's redirecting back to the sign-in/sign-up page.

Background

The sign-up/sign-in policy works successfully but as per Microsoft docs: https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-policies:

" A sign-up or sign-in user flow with local accounts includes a Forgot password? link on the first page of the experience. Clicking this link doesn't automatically trigger a password reset user flow.

Instead, the error code AADB2C90118 is returned to your application. Your application needs to handle this error code by running a specific user flow that resets the password. To see an example, take a look at a simple ASP.NET sample that demonstrates the linking of user flows. "

Active Directory B2C Settings

AppSettings

UserFlows

UserFlows

Code

OpenIdEvent

protected virtual Task OnRemoteFailure(RemoteFailureContext context)
{
    context.HandleResponse();
    // Handle the error code that Azure AD B2C throws when trying to reset a password from the login page
    // because password reset is not supported by a "sign-up or sign-in policy"
    if (context.Failure is OpenIdConnectProtocolException && context.Failure.Message.Contains("AADB2C90118"))
    {
        // If the user clicked the reset password link, redirect to the reset password route
        context.Response.Redirect("/Home/ResetPassword");                
    }
    else if (context.Failure is OpenIdConnectProtocolException && context.Failure.Message.Contains("access_denied"))
    {
        context.Response.Redirect("/");
    }
    else
    {
        context.Response.Redirect("/Home/Error?message=" + WebUtility.UrlEncode(context.Failure.Message));                
    }          

    return Task.FromResult(0);
}

HomeController

public IActionResult ResetPassword()
{
    var redirectUrl = Url.Action(nameof(HomeController.Index), "Home");
    var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
    properties.Items[AzureADB2COptionsExtended.PolicyAuthenticationProperty] = _adb2cOptions.ResetPasswordPolicyId;
    return Challenge(properties, AzureADB2CDefaults.AuthenticationScheme);
}

OpenIdSignInSignUp

A lot of examples I found use OWIN... There is very limited documentation on ASP.Net Core 2.2 w/ ADB2C.

3
So what is this value AzureADB2COptionsExtended.PolicyAuthenticationProperty ?jreancsu

3 Answers

0
votes

The Sign-up-sign-in policy now has built-in support for password resets without a second "password-reset" user flow. It is quite confusing with all the documentation and samples out there but this is the latest docs and it works for us!

https://docs.microsoft.com/en-us/azure/active-directory-b2c/force-password-reset?pivots=b2c-user-flow

0
votes

Answer that was posted in the question:

For anyone else having the same or similar issue, make sure to keep an eye out on the OpenIdConnectEvents. We have been experimenting with ADB2C/OpenID and had test code. This code was obviously invalid.

protected virtual Task OnRedirectToIdentityProvider(RedirectContext context)
{
    string policy = "";
    context.Properties.Items.TryGetValue(AzureADB2COptionsExtended.PolicyAuthenticationProperty, out policy);
    if (!string.IsNullOrEmpty(policy) && !policy.ToLower().Equals(_adb2cOptions.DefaultPolicy.ToLower()))
    {
        context.ProtocolMessage.Scope = OpenIdConnectScope.OpenId;
        context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken;
        context.ProtocolMessage.IssuerAddress = context.ProtocolMessage.IssuerAddress.ToLower().Replace(_adb2cOptions.DefaultPolicy.ToLower(), policy.ToLower());
    }
    return Task.FromResult(0);
}
0
votes

Normally, the ResetPassword flow you configured in your appsettings.json is called automatically when using the Microsoft.Identity.Web package. In your case B2C_1_SSPR. That means you must define a custom user flow with this Id. (I guess SSPR = self-service password reset)

The only thing you need in this default case is call the following in your Startup:

services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "ActiveDirectoryB2C");

This works like a charm.

However, you decided to deal with all this stuff by yourself and not to use the Microsft.Identity.Web library for all processing. In this case you are able to handle Password-reset by yourself.

But let's have a look at the Microsft.Identity.Web. They integrated an AccountController that handles the processing of the B2C custom flows (in this case the ResetPassword action).

The exception handling for AADB2C90118 can be found in the source code of the Identity package:

 if (isOidcProtocolException && message.Contains(ErrorCodes.B2CForgottenPassword))
 {
     // If the user clicked the reset password link, redirect to the reset password route
     context.Response.Redirect($"{context.Request.PathBase}/MicrosoftIdentity/Account/ResetPassword/{SchemeName}");
 }