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

[Bug] Starting release 1.11.0 authentication against Azure B2C using PKCE is broken. #1294

Closed tedvanderveen closed 3 years ago

tedvanderveen commented 3 years ago

Which version of Microsoft Identity Web are you using? Microsoft.Identity.Web 1.14.0

Where is the issue?

Is this a new or an existing app? The app is in production and I have upgraded to a new version of Microsoft Identity Web.

Repro

            services
                .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(options =>
                {
                    options.UsePkce = true;
                    options.ClientId = _configuration.GetValue<string>("B2C_CLIENT_ID");
                    options.Domain = $"{_configuration.GetValue<string>("B2C_TENANT_NAME")}.onmicrosoft.com";
                    options.Instance = $"https://{_configuration.GetValue<string>("B2C_TENANT_NAME")}.b2clogin.com";
                    options.TenantId = _configuration.GetValue<string>("B2C_TENANT_ID");
                    options.SignUpSignInPolicyId = "b2c_1_signup_signin";
                    options.ResetPasswordPolicyId = "b2c_1_passwordreset";
                    options.ResponseType = OpenIdConnectResponseType.Code;
                    options.Scope.Add(options.ClientId);
                });

Expected behavior Succesful authentication using Azure B2C PKCE capabilities

Actual behavior Up to Microsoft.Identity.Web version 1.10.0 authentication flow works fine. After upgrading to version 1.11.0 or later, B2C throws an exception.

Possible solution Add missing querystring fields to the call to B2C

Additional context / logs / screenshots Correct set of querystring parameters sent to B2C when using Microsoft.Identity.Web version 1.10.0: image

When using later versions of Microsoft.Identity.Web version, essential fields code_challenge and code_challange_method are no longer sent to B2C and authentication fails. image

tedvanderveen commented 3 years ago

Could this bug be introduced by the config options merging and refactoring? https://github.com/AzureAD/microsoft-identity-web/pull/1201

edwin-traffk commented 3 years ago

@tedvanderveen , @jennyf19, @jmprieur - as the use case for SPA application includes BCP from IETF for utilization of auth-code with PKCE flow for PUBLIC clients as a MUST (while Confidential clients are still a SHOULD) as well - is the intention here to split the required functionality to provide this between MSAL.NET and MSIdentityWeb for .net5? This is very applicable in the context for Blazor WASM applications. (MS has used the same logic to recommend MSAL2.js / indeed VERY strongly for some time - same capabilities).

I have followed several of the related dev issues in MSAL.NET, and this repo over the last several months - and I cannot discern the path.

jmprieur commented 3 years ago

@edwin-traffk : sorry, I don't understand what your question is (or your concern)

edwin-traffk commented 3 years ago

@jmprieur - my apologies for being unclear...

Background: We have a Blazor WASM (public client / SPA by definition) app - using the .NET MSAL libraries today. Currently there is no option to utilize 'auth-code + PKCE' flow (for public clients) from the .NET libraries (that I am aware of - OTHER than working out some kind of JSInterop with MSAL2.JS).

I am attempting to understand with the migration to .NET5/.NET6 - where this functionality will be applied to the straight .NET infrastructure (without JSInterop/calling MSAL2.JS). It happens that this is a B2C app as well (just further details).

I attempted to pick the most relevant repo/comment set/issue to reach the folks that seem to be the closest to the ongoing work that may affect this - please redirect/advise as appropriate. Thank you in advance.

tedvanderveen commented 3 years ago

@edwin-traffk this .Net lib actually does support Auth-code plus PKCE. We have it in production ATM. Just stick to version 1.10.0 (or earlier) for the moment.

tedvanderveen commented 3 years ago

@jmprieur was this a regression test miss or was PKCE support dropped on purpose with release 1.11.0? UsePkce is still available in the options.

jmprieur commented 3 years ago

Microsoft.Identity.Web should support AuthCode + PKCE, and I couldn't repro the issue so far (looking with fiddler in our B2C test app it does send the challenge). Do you have specific repro steps? Is it B2C only?

edwin-traffk commented 3 years ago

@tedvanderveen - thank you for the comment/feedback - I did notice this earlier in the related threads, and am glad to understand that at least the logic is there to some degree, and I could work it backwards to integrate. :)

Also - @jmprieur - I did note that this particular issue seemed to be related to regression from 1.10->current (so in theory - I could potentially grab MSIW 1.10 (for now - or later after regression fix) and see about hacking it together with MSAL.NET into some workable fashion for WASM (.net core fashion of auth vs. the mvc fashion of auth - not attempting to be pedantic here)).

I am not disputing the fact that it works in 1.10 with MSIW mvc - I haven't tested it (no repro fail - I can do that - but as it's not the specific case I was looking at I am going along with that it should work utilizing MSIW (working at least in 1.10 [or 1.15+ post bug fix] based upon comments so far by @tedvanderveen and others.) To be complete in my response to your last question - yes - for me B2C specifically - however, I would think that the flow from a library perspective is independent of what/who the provider is, though I understand MS* libraries would be 'tuned' to the MS offerings for easier integration from a development perspective - such as AAD,B2B,B2C,etc... I am concerned with the notes on the need for the 'implicit' flow to be configured w/ B2C - however, that doesn't seem to be the case based upon the notes/references I've seen so far.

For reference - I also have observed several other issues/discussions that have occurred around this topic with several of the same folks in MSAL.NET directly and/or MSIW repos (over the last several months/year or so) - I just happened to select this issue as a focus for my inquiry - as it seemed to have a more recent set of relevant/common participants with discussion of same, that have been in the others as well.

That said - being that my usage case is a little different - using BlazorWASM SPA (public-client) - not [yet] utilizing the MSIW library, or server-side hosting (confidential client that can maintain a secret) - I was more-so hoping for some clarification and/or guidance on what the path was to support this flow going forward in .NET5/.NET6 (WASM public client side SPA with auth-code/PKCE flow).

I see a few options (I'm sure there are more - happy to have feedback - much appreciated :) ...

I would imagine this would be [eventually be put] in [a|a set of] templates - or sample projects - so that it is indeed the recommended code path for usage with same (WASM public-client SPA).

This is effectively all in effort to understand when (or indeed the path that this functionality/feature will likely take forward into) the .NET5/.NET6 libraries/tooling (specifically for Blazor WASM public-client SPA applications) and will fully support the auth-code/pkce flow out of the box. In an effort to avoid selecting an alternative path and hacking something together in the meantime before we get parity between MSAL2.JS functionality (ultimately for this specific feature and associated relevant components - like token refresh, etc... due to latest browser issues with implicit flow and ITP/3rd parties/etc...) and .NET5/.NET6 native .NET functionality.

Again - many thanks to all of you - I appreciate the feedback and assistance? I'm happy to provide additional context if necessary, or I'm missing anything? Best Regards.

tedvanderveen commented 3 years ago

Microsoft.Identity.Web should support AuthCode + PKCE, and I couldn't repro the issue so far (looking with fiddler in our B2C test app it does send the challenge). Do you have specific repro steps? Is it B2C only?

@jmprieur can you please share config/options and B2C setup? So I can retry. And yes, it's B2C only. Regarding repo steps, we had v.1.10.0 deployed and working. Just upgrading to next minor version, caused the required PKCE related querystring parameters to not be sent.

edwin-traffk commented 3 years ago

@jmprieur - apologies for delay on a repro - starting anew with clean .NET5 latest release (5.0.302sdk) was able to use .NET5 blazorwasm template with --auth B2C - adding a few scopes to the request (in program.cs) - and get id, refresh, access tokens using auth-code/pkce from the B2C endpoint configured as a SPA redirect with 'public client' and NO selection for 'id token' or 'access token' in the portal. @tedvanderveen (thanks again as well for the pointer w.r.t. portal config), no client secret required.

edwin@mcl-u1:~/working/dotnet/wasm-b2c-2$ dotnet --info .NET SDK (reflecting any global.json): Version: 5.0.302 Commit: c005824e35

Runtime Environment: OS Name: ubuntu OS Version: 20.04 OS Platform: Linux RID: ubuntu.20.04-x64 Base Path: /usr/share/dotnet/sdk/5.0.302/

Host (useful for support): Version: 5.0.8 Commit: 35964c9215

.NET SDKs installed: 3.0.103 [/usr/share/dotnet/sdk] 3.1.411 [/usr/share/dotnet/sdk] 5.0.302 [/usr/share/dotnet/sdk]

.NET runtimes installed: Microsoft.AspNetCore.App 3.0.3 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.17 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.8 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.NETCore.App 3.0.3 [/usr/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.17 [/usr/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.8 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

image

tedvanderveen commented 3 years ago

@edwin-traffk what version of this library are you using? 1.10.0 or latest (1.14.1)?

edwin-traffk commented 3 years ago

@tedvanderveen - I was thinking (in error it appears) - that I would have to supplement the Microsoft.Authentication.WebAssembly.Msal with MSIW (some version) or other library, in order to get pkce to function correctly. (at least the initial exchange, etc...). I'm not exactly sure if any/parts of MSIW were included into the .NET5 WebAssembly packages to provide this functionality - and if so, what version.

Here is my .csproj

edwin@mcl-u1:~/working/dotnet/wasm-b2c-2$ cat wasm-b2c-2.csproj

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.8" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.8" PrivateAssets="all" />
    <PackageReference Include="Microsoft.Authentication.WebAssembly.Msal" Version="5.0.8" />
    <PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
  </ItemGroup>

</Project>
tedvanderveen commented 3 years ago

@edwin-traffk I guess what your setup is using, is based on this: https://github.com/dotnet/aspnetcore/tree/main/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src This is component is supposed to be replaced by MSIW. But for now this deprecated lib could be a valid alternative while PKCE support is being restored

jmprieur commented 3 years ago

@tedvanderveen @edwin-traffk So I spent the time needed to investigate what happens.

Analysis

To come back to the original issue, PKCE will only be used, indeed, if

options.ResponseType = OpenIdConnectResponseType.Code; or options.ResponseType = OpenIdConnectResponseType.CodeIdToken;

but this seems to provoke a token validation error in ASP.NET Core / Identity.Model. There were a lot of moving parts since 1.10, among which a strong validation (to avoid security issues).

Work around

To work around this issue, just enable token acquisition (even if you don't use it). Add the last 2 lines in the code snippet belwo in your startup.cs method.

    services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(Configuration, "AzureAdB2C")
                    .EnableTokenAcquisitionToCallDownstreamApi()
                        .AddInMemoryTokenCaches()  ;

If you have questions about blasorwasm

If you have questions about blasorwasm, please ask them on the ASP.NET core repo, as Microsoft.AspNetCore.Components.WebAssembly is managed from there: https://github.com/aspnet/aspnetcore

If you want

tedvanderveen commented 3 years ago

@jmprieur thanks for the deeper analysis! So no changes need to be made on the B2C side in Azure? It's just those two additional lines in startup.cs?

tedvanderveen commented 3 years ago

Additionally, what is the purpose of the UsePkce setting now actually? I appreciate API of this lib has changed between versions 1.10.0 and 1.11.0. Shall there be any updated documentation or guidance be provided to accompany those changes?

tedvanderveen commented 3 years ago

And finally, I find it a bit strange this issue/problem/bug/regression was now suddenly labeled "Question" and promptly labeled "Answered" while clearly something is not okay and some (temporary?) workaround is required to mitigate a breaking and not fully tested change.

jmprieur commented 3 years ago

@tedvanderveen :

I could have labeled it "by design". I've spent a couple of hours analyzing what happens, and this is as designed. I didn't try with a previous version of Identity.Model, though (that's a dependency for us and recent versions of Identity.Model have resilency updates that we want everybody to have so it's not an option to revert back to an earlier version of that library.

Indeed, we didn't test the scenario that we weren't expect. (use pkce to just sign-in a user, because as I wrote, in that case you don't just sign-in. you also get a token)

tedvanderveen commented 3 years ago

Hi @jmprieur I'm back from vacation and onto this issue again. When I add those two lines to startup.cs, I get an error: IDW10104: Both client secret and client certificate cannot be null or whitespace, and only ONE must be included in the configuration of the web app when calling a web API. For instance, in the appsettings.json file..

How can I obtain an access token from B2C using client_id only? I want to use the refresh token and call on B2C to obtain a fresh access token. When using PKCE (non-confidential app) in B2C a refresh token is always provided. And one only requires a client_id to obtain a new access token:

POST https://{removed}.b2clogin.com/{removed}.onmicrosoft.com/b2c_1_signup_signin/oauth2/v2.0/token?p=b2c_1_signup_signin HTTP/1.1
Host: {removed}.b2clogin.com
User-Agent: Microsoft ASP.NET Core OpenIdConnect handler
traceparent: 00-12e2cfaf1f2bfd4daab716cfa715f1ac-beda6a6f3ccd8d4f-00
Content-Type: application/x-www-form-urlencoded
Content-Length: 1156

grant_type=refresh_token&refresh_token=eyJraWQiOiJjcGltY29{removed}BxvLG5oeySeDA&client_id=79d4{removed}0abcb&redirect_uri=https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&scope=openid 79d4{removed}0abcb

HTTP/1.1 200 OK
Cache-Control: private
Allow: OPTIONS,TRACE,GET,HEAD,POST
Content-Type: application/json; charset=utf-8
x-ms-gateway-requestid: e65aa40c-3fcf-4390-9415-c99abfc49681
Set-Cookie: x-ms-cpim-trans=; domain={removed}.b2clogin.com; expires=Thu, 28-Jul-2011 14:05:54 GMT; path=/; SameSite=None; secure; HttpOnly
X-Frame-Options: DENY
Public: OPTIONS,TRACE,GET,HEAD,POST
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Date: Wed, 28 Jul 2021 14:05:54 GMT
Content-Length: 3803

{"access_token":"eyJ0eXAiOiJKV1QiLCJ{removed}cmTY2gEqWcn3Lw","id_token":"eyJ0eXAiOiJKV1{removed}KFWBVrNYxk3nNtA","token_type":"Bearer","not_before":1627481154,"expires_in":3600,"expires_on":1627484754,"resource":"79d4{removed}0abcb","id_token_expires_in":3600,"profile_info":"eyJ2ZXIiOiIxLjA{removed}wIjpudWxsfQ","scope":"/ openid","refresh_token":"eyJraWQiOiJjcGltY29y{removed}f1ExD-JWYJw","refresh_token_expires_in":85473}

Should we implement a custom ITokenAcquisition implementation that does this? As the built-in class TokenAcquisition somehow has a hard dependency on the availability of a "confidential" app, although B2C does NOT require client_secret or cert. See this hard dependency here: https://github.com/AzureAD/microsoft-identity-web/blob/14429b8cb93352d78b6e1618d3a0bb50f203602d/src/Microsoft.Identity.Web/TokenAcquisition.cs#L651

@tedvanderveen :

  • UsePkce is used when you want to acquire a token to call a downstream web API, not when you 'just' want to sign-in a user (because you would get an access token anyway). You would btw need to provide a client secret or a client certificate.
  • therefore if you really want to sign-in user and not do anything with the token the suggestion is to be explicit about it (add the 2 lines to enable token acquisition).

I could have labeled it "by design". I've spent a couple of hours analyzing what happens, and this is as designed. I didn't try with a previous version of Identity.Model, though (that's a dependency for us and recent versions of Identity.Model have resilency updates that we want everybody to have so it's not an option to revert back to an earlier version of that library.

Indeed, we didn't test the scenario that we weren't expect. (use pkce to just sign-in a user, because as I wrote, in that case you don't just sign-in. you also get a token)

tedvanderveen commented 3 years ago

I have created a new issue here: #1352 for adding support for AAD B2C token acquisition without client_secret or cert.