AzureAD / microsoft-identity-web

Helps creating protected web apps and web APIs with Microsoft identity platform and Azure AD B2C
MIT License
679 stars 210 forks source link

[question] Redirect to plain b2clogin.com page from callback #1367

Open avdv opened 3 years ago

avdv commented 3 years ago

Hi.

I am not sure this is a bug, please point me into the right direction if not.

We are using Azure AD B2C with a .NET 5 Webapp, which does only login users. We have version 1.15.2, but I think it also happened with version 1.14.1.

Setup:

"AzureAd": {
    "Instance": "https://xxx.b2clogin.com",
    "ClientId": "...",
    "Domain": "xxx.onmicrosoft.com",
    "SignedOutCallbackPath": "/protected/signedout",
    "SignUpSignInPolicyId": "B2C_1A_signup_signin",
    "ResetPasswordPolicyId": "B2C_1A_PasswordReset",
    "EditProfilePolicyId": "B2C_1A_ProfileEdit",
    "Scope": [ "openid", "profile", "offline_access"],
    "CallbackPath": "/protected/callback"
  },
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(
                    options =>
                    {
                        Configuration.Bind("AzureAd", options);
                    },
                    cookieScheme: null,
                    subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true
                );

EditProfile handler in our AccountController:

scheme ??= OpenIdConnectDefaults.AuthenticationScheme;

var properties = new AuthenticationProperties { RedirectUri = redirectUrl };

MicrosoftIdentityOptions microsoftIdentityOptions = _identityOptionsMonitor.Get(scheme);

properties.Items[Constants.Policy] = microsoftIdentityOptions.EditProfilePolicyId;

return Challenge(properties, scheme);

In the edit-profile flow, after hitting "Save" on the b2c screen, the given redirect URI callback is called but instead of redirecting to the given redirectUri of the authentication properties, it simply redirects to https://xxx.b2clogin.com/ which displays "The resource you are looking for has been removed, had its name changed, or is temporarily unavailable".

AFAIU, the callbackPath route is handled internally by the identity middleware, right?

Any help would be appreciated.

jennyf19 commented 3 years ago

@avdv can you try with the default callbackpath: /signin-oidc ?

avdv commented 3 years ago

can you try with the default callbackpath: /signin-oidc ?

I'll have to request that this callback is added in Azure, and deploy a proxy change that this path is forwarded to the webapp. I'll report back once I have results.

avdv commented 3 years ago

@jennyf19 I had the callback URL added in Azure, and configured forwarding in the proxy / load balancer. Then, I set AzureAd:CallbackPath to /signin-oidc in the application settings in Azure.

Overall, this made no difference:

POST | https://hostname.azurewebsites.net/signin-oidc

=>

302 Found
Location: https://xxx.b2clogin.com/

But, I now better understand what's going on:

  1. browser requests edit profile flow: GET /identity/account/editprofile
  2. browser gets redirected to the signup / signin endpoint first: 302 https://xxx.b2clogin.com/xxx.onmicrosoft.com/b2c_1a_signup_signin/oauth2/v2.0/authorize?client_id=...
  3. IdP authorizes user: POST /signin-oidc (already logged in)
  4. browser gets redirected to edit profile again: 302 /identity/account/editprofile
  5. browser gets redirected: 302https://xxx.b2clogin.com/xxx.onmicrosoft.com/b2c_1a_profileedit/oauth2/v2.0/authorize?client_id=...`
  6. edit profile, hit save: POST https://xxx.b2clogin.com/ ; `GET https://xxx.b2clogin.com/confirmed?...
  7. callback call: `POST /signin-oidc

In step 4, the original RedirectUri from step 1 is overriden, since a new challenge is created.

public async Task<IActionResult> EditProfile([FromRoute] string scheme, [FromQuery] string? returnUrl)

If the returnUrl is not given in the query string, we use the referer by default, which explains why the browser is redirected to the b2clogin.com domain.

How can I maintain the given returnUrl here?

avdv commented 3 years ago

Maybe also the question is, for this code: https://github.com/AzureAD/microsoft-identity-web/blob/7fc0499b48dccc6876bf5079985495bd30348ac4/src/Microsoft.Identity.Web.UI/Areas/MicrosoftIdentity/Controllers/AccountController.cs#L154-L162


why does it (almost) always fail the AuthenticateAsync call at first?

And why is that call necessary at all, since for reset password  it does not first try to authenticate (again).
avdv commented 3 years ago

I have now changed our EditProfile route to this:

[HttpGet("{scheme?}")]
[Authorize, RequireHttps]
public async Task<IActionResult> EditProfile([FromRoute] string scheme, [FromQuery] string? returnUrl)
{
     scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
     var redirectUrl = SafeReturnUrl(HttpContext.Request, returnUrl);
     var properties = new AuthenticationProperties { RedirectUri = redirectUrl,  };

     MicrosoftIdentityOptions microsoftIdentityOptions = _identityOptionsMonitor.Get(scheme);

     properties.Items[Constants.Policy] = microsoftIdentityOptions.EditProfilePolicyId;

     return Challenge(properties, scheme);
}

Ie. I have removed the extra AuthenticateAsync call and just create the challenge for the edit profile directly. This works fine for us, and keeps the returnUrl intact.

In case the user happens to be no longer authenticated with the B2C server, it displays the idp selector and requests credentials by itself.

Am I missing something why this would be a bad idea? Thanks!