Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error retrieving the current token in a Blazor application after successful authentication in Azure B2C with Msal #39311

Closed
vsfeedback opened this issue Jan 4, 2022 · 58 comments
Assignees
Labels
area-blazor Includes: Blazor, Razor Components Author: Migration Bot 🤖 The issue was created by a issue mover bot. The author may not be the actual author. feature-blazor-wasm-auth
Milestone

Comments

@vsfeedback
Copy link

vsfeedback commented Jan 4, 2022

This issue has been moved from a ticket on Developer Community.


I have created a Blazor application and I use Msal to do authentication following the examples from Azure B2C.
With my B2C configuration in the server, I can successfully login, I get the User, the claims and I can call the api.
I would need to have access to the token during development to be able to use that token to call my api services directly using swagger. So during Debugging I wanted to print the token on screen to copy and paste.

However, when I try to get the token in the Client after successful login, I get an Exception:
An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires

This is a very confusing exception and doesn't seem to explain the type of error that is occurring. It seems that is not doing any validation during the parsing or pre-parsing. So not sure why this is happening.

More details:

I initialise with this:

builder. Services.AddMsalAuthentication(options =>
            {
                builder. Configuration.Bind("AzureAdB2C", options. ProviderOptions.Authentication);

//options. ProviderOptions.DefaultAccessTokenScopes.Add("02200220-20202-2020-2020-202020200202002"); // I tried to put the application Id a per documentation and it doesn't work

options. ProviderOptions.DefaultAccessTokenScopes.Add("openid");
                options. ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");

// request scope to access the API
                options. ProviderOptions.AdditionalScopesToConsent.Add("https://myb2c.onmicrosoft.com/whateverApp/MyAPI");

options. ProviderOptions.LoginMode = "redirect";
            });

I also use a custom AuthorizationMessageHandler to be able to call the api, which works well.

However, when in my code I call the code to retrieve the Token, I get the exception + one unhandled exception:

[Inject]
public IAccessTokenProvider TokenProvider { get; set; }
...
var accessTokenResult = await TokenProvider.RequestAccessToken(); // <-- This throws exception

And additionally there is an unhandled exception:

Unhandled Exception:
System.Text.Json.JsonException: Invalid JSON
   at Microsoft.JSInterop.Infrastructure.DotNetDispatcher.EndInvokeJS(JSRuntime jsRuntime, String arguments)
   at Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime.<>c.<EndInvokeJS> b__7_0(String argsJson)
   at Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyCallQueue.Schedule[String](String state, Action`1 callback)
   at Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime.EndInvokeJS(String argsJson)
Uncaught Error: System.Text.Json.JsonException: Invalid JSONThe thread 0x1b444 has exited with code 0 (0x0).

I have tried in B2C to set the configuration, and I'm able to run the SignIn flow redirecting to jwt.ms passing the token and decoding it.

image.png

And I get the token decoded when redirecting directly to jwt.ms:

image.png


Original Comments

Feedback Bot on 11/12/2021, 00:15 AM:

We have directed your feedback to the appropriate engineering team for further evaluation. The team will review the feedback and notify you about the next steps.


Original Solutions

(no solutions)

@ghost ghost added the Author: Migration Bot 🤖 The issue was created by a issue mover bot. The author may not be the actual author. label Jan 4, 2022
@mkArtakMSFT mkArtakMSFT added area-blazor Includes: Blazor, Razor Components feature-blazor-wasm-auth labels Jan 4, 2022
@mkArtakMSFT
Copy link
Member

mkArtakMSFT commented Jan 10, 2022

Thanks for contacting us. This is already fixed as part of #38962 / #39060.

@mkArtakMSFT mkArtakMSFT added the ✔️ Resolution: Duplicate Resolved as a duplicate of another issue label Jan 10, 2022
@ghost ghost added the Status: Resolved label Jan 10, 2022
@TanayParikh
Copy link
Contributor

Please note, the fix for this issue should be available in the 6.0.2 patch release.

@rmencia-isv
Copy link

Any estimates on this?
I've got this installed 6.0.200-preview.21617.4 and still not working

@TanayParikh
Copy link
Contributor

The fix for the underlying System.Text.Json.JsonException: Invalid JSON exception was backported through #39075.

@rmencia-isv could you please try this out using the latest installer available at https://github.com/dotnet/installer. Please let us know if you're still having the issue with that, and please provide the exact version of the dotnet sdk being used, along side the exact exception message you're getting.

@rmencia-isv
Copy link

I have installed the latest versionfrom the url you provided (dotnet-sdk-6.0.200-preview.22068.4-win-x64.exe) and I still get the same error in Blazor wasm client app.

Checking the list I have this one installed (6.0.200-preview.22068.4)
However, by looking at the call stack I see references to Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.1.0*

The error Message:
An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 73.. See InnerException for more details.

InnerException:
at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, Utf8JsonReader& reader, Exception ex)
at System.Text.Json.Serialization.JsonConverter1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InternalAccessTokenResult, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.Serialization.JsonConverter1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InternalAccessTokenResult, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ReadCoreAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadCore[Object](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.Read[Object](Utf8JsonReader& reader, JsonTypeInfo jsonTypeInfo)
at System.Text.Json.JsonSerializer.Deserialize(Utf8JsonReader& reader, Type returnType, JsonSerializerOptions options)
at Microsoft.JSInterop.JSRuntime.EndInvokeJS(Int64 taskId, Boolean succeeded, Utf8JsonReader& jsonReader)'

@TanayParikh
Copy link
Contributor

by looking at the call stack I see references to Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.1.0*

Ah that's definitely a bit strange. Could you please double check your csproj files to ensure you don't have any hardcoded versions which may be causing this issue? Also, if you could try adding the following to your nuget config:

<add key="dotnet6" value="https://dnceng.pkgs.visualstudio.com/public/_packaging/dotnet6/nuget/v3/index.json" />

Further details here.

@rmencia-isv
Copy link

This is what I have in the project file.
I also added the nuget in VS to get the packages from that place and no new updates available, even pre-release for any of those packages.

image

image

@TanayParikh
Copy link
Contributor

Hey @rmencia-isv, I took a look at the feeds and you're right the 6.0.2 (prerelease) packages aren't up yet. You can either try waiting for the official 6.0.2 release (mid February), or try the 7.0 alpha packages (not sure if they'll be updated yet though).

@rmencia-isv
Copy link

Thanks Tanay for getting back to me. It's unfortunate that the packages are not uploaded yet, as I've seen a number of issues fixed.
I'll probably wait for the release next month, if the pre release packages are not updated.

@kbeaugrand
Copy link

Hi, @TanayParikh ,

I'm facing the same issue. Unfortunately I cannot wit until mid February.
Is there any chance to test the 6.0.2 (prerelease) you mentioned.

This is definitively not present on the repo you suggested...

@TanayParikh
Copy link
Contributor

Hello @kbeaugrand, to test out the pre-release you can try using the nightly SDK & packages as above, or by using the 7.0 alpha/beta packages.

SDK Download: https://github.com/dotnet/installer (ensure you get the 6.0.2 or 7 alpha/beta SDK)

Add to your Nuget config:

<add key="dotnet6" value="https://dnceng.pkgs.visualstudio.com/public/_packaging/dotnet6/nuget/v3/index.json" />

Further details here.

@Pete-Nago
Copy link

I also am having the exact same issue with B2C and Blazor WASM.
Hope they release the fix soon.
I'm hesitant to install preview builds.

@kbeaugrand
Copy link

Hi @TanayParikh,

Do you have a more precise date for the release of Microsoft.AspNetCore.Components.WebAssembly.Authentication v6.0.2 ?

Thank you.

@TanayParikh
Copy link
Contributor

Hi @TanayParikh,

Do you have a more precise date for the release of Microsoft.AspNetCore.Components.WebAssembly.Authentication v6.0.2 ?

Thank you.

It was just released earlier today!

@kbeaugrand
Copy link

Hi @TanayParikh,
Do you have a more precise date for the release of Microsoft.AspNetCore.Components.WebAssembly.Authentication v6.0.2 ?
Thank you.

It was just released earlier today!

Thank you for your answer.
I checked it, but unfortunately I'm still facing the issue... ;(

@TanayParikh
Copy link
Contributor

unfortunately I'm still facing the issue

To confirm you've updated the SDK/.csproj to utilize the new 6.0.2 packages, correct? What exact error message are you seeing?

Can you please provide a minimal, public, github repro which reproduces this issue?

@kbeaugrand
Copy link

unfortunately I'm still facing the issue

To confirm you've updated the SDK/.csproj to utilize the new 6.0.2 packages, correct? What exact error message are you seeing?

Can you please provide a minimal, public, github repro which reproduces this issue?

Hi,

Yes I confirm that I updated my package to 6.0.2 (https://github.com/CGI-FR/IoT-Hub-Portal/blob/main/src/AzureIoTHub.Portal/Client/AzureIoTHub.Portal.Client.csproj).
I'm using OpenID connect.

You can find a public repos (not minimal) with my configuration here: https://github.com/CGI-FR/IoT-Hub-Portal/.

For the code sample, that is relevant, you can find at client Program.cs file the executed configuration: https://github.com/CGI-FR/IoT-Hub-Portal/blob/main/src/AzureIoTHub.Portal/Client/Program.cs.

The error message obtained is:

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 170.. See InnerException for more details.
Microsoft.JSInterop.JSException: An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 170.. See InnerException for more details.
 ---> System.Text.Json.JsonException: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 170.
 ---> System.InvalidOperationException: Cannot get the value of a token type 'Null' as a string.
   at System.Text.Json.Utf8JsonReader.TryGetDateTimeOffset(DateTimeOffset& value)
   at System.Text.Json.Utf8JsonReader.GetDateTimeOffset()
   at System.Text.Json.Serialization.Converters.DateTimeOffsetConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1[[System.DateTimeOffset, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.AccessToken, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, AccessToken& value)
   at System.Text.Json.Serialization.JsonConverter`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.AccessToken, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, AccessToken& value)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.AccessToken, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InternalAccessTokenResult, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, InternalAccessTokenResult& value)
   at System.Text.Json.Serialization.JsonConverter`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InternalAccessTokenResult, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, InternalAccessTokenResult& value)
   at System.Text.Json.Serialization.JsonConverter`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InternalAccessTokenResult, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, Utf8JsonReader& reader, Exception ex)
   at System.Text.Json.Serialization.JsonConverter`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InternalAccessTokenResult, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.JsonConverter`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InternalAccessTokenResult, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ReadCoreAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[Object](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.Read[Object](Utf8JsonReader& reader, JsonTypeInfo jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Deserialize(Utf8JsonReader& reader, Type returnType, JsonSerializerOptions options)
   at Microsoft.JSInterop.JSRuntime.EndInvokeJS(Int64 taskId, Boolean succeeded, Utf8JsonReader& jsonReader)
   --- End of inner exception stack trace ---
   at Microsoft.JSInterop.JSRuntime.<InvokeAsync>d__16`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InternalAccessTokenResult, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].MoveNext()
   at Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationService`3.<RequestAccessToken>d__22[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationState, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteUserAccount, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[Microsoft.AspNetCore.Components.WebAssembly.Authentication.OidcProviderOptions, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=6.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].MoveNext()
   at Microsoft.AspNetCore.Components.WebAssembly.Authentication.AuthorizationMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at System.Net.Http.Json.HttpClientJsonExtensions.<GetFromJsonAsyncCore>d__13`1[[AzureIoTHub.Portal.Shared.Models.Device.DeviceListItem[], AzureIoTHub.Portal.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
   at AzureIoTHub.Portal.Client.Pages.Devices.DeviceListPage.LoadDevices() in C:\Users\beaugrandk\Source\Repos\CGI-FR\IoT-Hub-Portal\src\AzureIoTHub.Portal\Client\Pages\Devices\DeviceListPage.razor:line 179
   at AzureIoTHub.Portal.Client.Pages.Devices.DeviceListPage.OnInitializedAsync() in C:\Users\beaugrandk\Source\Repos\CGI-FR\IoT-Hub-Portal\src\AzureIoTHub.Portal\Client\Pages\Devices\DeviceListPage.razor:line 163
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
window.Module.s.printErr @ blazor.webassembly.js:1```

@kbeaugrand
Copy link

I may be able to reproduce the issue on a minimal project if needed.

@TanayParikh
Copy link
Contributor

I may be able to reproduce the issue on a minimal project if needed.

Yes please, that'd be much appreciated!

Thanks for the stack trace. I see the underlying System.Text.Json.JsonException: Invalid JSON is resolved (via backported #39075), but we still have the

Microsoft.JSInterop.JSException: An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 170.. See InnerException for more details.
 ---> System.Text.Json.JsonException: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 170.
 ---> System.InvalidOperationException: Cannot get the value of a token type 'Null' as a string.

error. Re-opening this issue.

@TanayParikh TanayParikh reopened this Feb 9, 2022
@TanayParikh TanayParikh removed ✔️ Resolution: Duplicate Resolved as a duplicate of another issue Status: Resolved labels Feb 9, 2022
@kbeaugrand
Copy link

kbeaugrand commented Feb 9, 2022

I may be able to reproduce the issue on a minimal project if needed.

Yes please, that'd be much appreciated!

Thanks for the stack trace. I see the underlying System.Text.Json.JsonException: Invalid JSON is resolved (via backported #39075), but we still have the

Microsoft.JSInterop.JSException: An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 170.. See InnerException for more details.
 ---> System.Text.Json.JsonException: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 170.
 ---> System.InvalidOperationException: Cannot get the value of a token type 'Null' as a string.

error. Re-opening this issue.

I'll send you the sample app as soon I've pushed on a public repos.

@javiercn
Copy link
Member

@kbeaugrand here is a sample that shows how you can customize the JS we provide. It uses MSAL, but the steps are equivalent for OpenID Connect.

https://github.com/javiercn/BlazorWasmMsalSample

@andrewwilkin
Copy link

andrewwilkin commented May 13, 2022

Looking at the sample:
interface AccessToken { value: string; expires: Date | null; grantedScopes: string[]; }

I presume from the error previously that the AccessToken has null expiry and thus cannot be converted to the DateTimeOffset by the serializer:
An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 73.. See InnerException for more details. Microsoft.JSInterop.JSException: An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 73.. See InnerException for more details. ---> System.Text.Json.JsonException: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 73. ---> System.InvalidOperationException: Cannot get the value of a token type 'Null' as a string.

Maybe it is line 164/165:
const result = await this.signInCore(request); if (!result) { return this.redirect(); } else if (this.isMsalError(result)) { return this.error(result.errorMessage); }

If the result contains something then it won't make the call on line 166. Not tried debugging it yet, thoughts?

@mkArtakMSFT mkArtakMSFT modified the milestones: 6.0.x, .NET 7 Planning May 20, 2022
@ghost
Copy link

ghost commented May 20, 2022

Thanks for contacting us.

We're moving this issue to the .NET 7 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@Spaceman1861
Copy link

Spaceman1861 commented May 22, 2022

If it helps anyone here is how I implemented a work around for this.

I needed to get the token to pass it to my API.

I added this to the index.html

<script>
    window.getJWT = () => JSON.parse(sessionStorage[
        Object
            .keys(sessionStorage)
            .find(_ => _.includes("yourdomain.b2clogin.com-idtoken"))
        ])
        .secret;
</script>

Then commented out the recommended message handler:

builder.Services.AddHttpClient(
    "WebAPI",
    (services,client) => {
        client.BaseAddress = new Uri("http://localhost:7071");
    }
);
// COMMENT THIS OUT
//.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

builder
    .Services
    .AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("WebAPI"));

Then when you use the Httpclient manually add it:

    [Inject]
    protected HttpClient? Http { get; set; }

    [Inject]
    protected IJSRuntime JS { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (Http == null)
            throw new ArgumentNullException(nameof(Http));

        if (JS == null)
            throw new ArgumentNullException(nameof(JS));

        var text = await JS.InvokeAsync<string>("getJWT", new object[] { });

        Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
            "Bearer",
            text
        );

        using var stream = await Http.GetAsync("http://localhost:7071/api/Function1");

    }

Its pretty janky but works for me until they roll out a fix :).

@kbeaugrand
Copy link

@Spaceman1861, this would add the id_token in the Authorization header, right ?
In that case, I guess the authentication should not pass to the backend since this is not the access_token for your backend audience...

@Spaceman1861
Copy link

Perhaps I'm looking at it wrong @kbeaugrand ill walk you through the train of though if you think I have it wrong id love to know:

  • User logins in via blazor front end.

  • Token stored in session in browser.

  • User needs to access separate function app backend and must be Authenticated.

  • Passing the JWT as bearer from the session and then validating it in the function app allows with code like this.

      public static async Task<JwtSecurityToken> ValidateAsync(string token)
      {
          string stsDiscoveryEndpoint = "https://YOURSITEHERE.b2clogin.com/YOURSITEHERE.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_Auth";
          var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever()); 
          
          //1. need the 'new OpenIdConnect...'
          OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
          TokenValidationParameters validationParameters = new TokenValidationParameters
          {
              //decode the JWT to see what these values should be
              ValidAudience = "SOMEGUID",  // Replaced values by XXXX
              IssuerSigningKeys = config.SigningKeys, //2. .NET Core equivalent is "IssuerSigningKeys" and "SigningKeys"
              ValidateIssuer = true,
              ValidIssuer = "https://YOURSITEHERE.b2clogin.com/SOMEGUID/v2.0/",
              ValidateAudience = true,
              ValidateIssuerSigningKey = true,
              RequireExpirationTime = true,
              ValidateLifetime = true
          };
          JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
          SecurityToken jwt;
          var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
          return jwt as JwtSecurityToken;
      }
    

@kbeaugrand
Copy link

@Spaceman1861

Can you print in the discussion, the tokens you have in the session storage (not the values, just the keys) ?
Maybe I miss understand the OAuth 2.0 (and OpenId protocol), but with the AspNetCore Assembly, I can only work with the id_token.
To my standing point, my WebAssembly App should be considered as a client app, and should act to my API on behalf of the user.
So my API refuse the id_token as the Bearer token because it has been issued for identification, not for authorization....

image
Source: https://auth0.com/blog/id-token-access-token-what-is-the-difference/.

@javiercn, I cannot understand the decision to move the fix to this issue to .NET 7.
What is the solution to use WebAssembly Authenticated (even in MSAL or OpenIdConnect) and provide the access token to the API part properly ?

I have two issues :

  • When requesting only the id_token from the authorization endpoint, the process works, but while requesting the access_token, the application fails with the $.token.expires issue
  • When requesting both id_token and token, the authentication fails, the browser tries 2 times to authenticate and redirects me to an error page (however the Identity provider sends correctly the id_token and access_token to the app).
    image

These two issues makes the Blazor WebAssembly not usable with an authenticated resource !

@Spaceman1861
Copy link

Spaceman1861 commented May 24, 2022

An interesting article.

I am indeed using the IdToken for Authentication.

{
    "credentialType": "IdToken",
    "homeAccountId": "SOME VALUE",
    "environment": "something.b2clogin.com",
    "clientId": "SomeGUIld",
    "secret": "JWT GOES HERE",
    "realm": ""
}

From that article

As said above, an ID token proves that a user has been authenticated. In a first-party scenario, i.e. in a scenario where the client and the API are both controlled by you, you may decide that your ID token is good to make authorization decisions: maybe all you need to know is the user identity.

However, even in this scenario, the security of your application, consisting of the client and the API, may be at risk. In fact, there is no mechanism that ties the ID token to the client-API channel. If an attacker manages to steal your ID token, they can use it to call your API like a legitimate client.

For reference I have 3 tokens in my session

  • Refresh
  • Id Token
  • some random one that seems to have information around the Id Token.

I assume this is the way i configured the B2C workflow to only return an Id Token.

This is adequate for my scenario for the time being.

@kbeaugrand
Copy link

I finally found what was going wrong. And if I'm right, this might had absolutely never work...

On the AuthenticationService, when requesting the AccessToken, it calls the OIDC client to silent sign in. This will request the IdP and expects to have the access_token in the response.
However, it takes in arguements the response_type provided by the application (by default : id_token).
Since the IdP recieves the id_token response_type, it will only return the idToken, never the access token...

To fix that I added the token in the arguments to pass to the signinSilent and MAGIC, it gets the access token, and everything was working (no other changes).

@kbeaugrand
Copy link

Nevermind, I got why getting the access token as part of the login doesn't work in my case...
In my case, I'm using OpenID connect with Azure AD B2C as the IdP.

AD B2C doesn't provide the user info endpoint and he oidc client used requires it ;(
It fails to perfom the sign in and validates the login but without any error message...

By changing the settings, I got my flow working (after disabling the loadUserInfo (see: https://github.com/IdentityModel/oidc-client-js/wiki#other-optional-settings)

After, that I can have my access token with the id_token.

@asieradzk
Copy link

Guys fix this?

@ekuhlmann23
Copy link

@kbeaugrand Could you provide more details on the workaround, please?

@kbeaugrand
Copy link

@ekuhlmann23,

I simply moved the settings to specify the authentication response type to ``code```.

e.g:

   _ = builder.Services.AddOidcAuthentication(options =>
            {
                ....
                options.ProviderOptions.ResponseType = "code";
            });

@javiercn javiercn modified the milestones: 7.0-rc1, 7.0-rc2 Aug 29, 2022
@Magik3a
Copy link

Magik3a commented Sep 1, 2022

So, using new Blazor project with default B2C configuration is still erroring on the profile page.
So lets wait as the other guys on this :@
image
image

@javiercn
Copy link
Member

We have made improvements to this scenario in .NET 7.0, the changes will be available in RC2.

It should be easier to diagnostic when there is an issue in the configuration of the app.

#43954

@ghost ghost locked as resolved and limited conversation to collaborators Oct 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components Author: Migration Bot 🤖 The issue was created by a issue mover bot. The author may not be the actual author. feature-blazor-wasm-auth
Projects
None yet