Call Protected APIs from a Blazor Web App

Calling a protected API from a .NET 8 Blazor Web App can be a bit tricky. Let’s see what the problems are and how to solve them.
Read more…

:writing_hand:t2: Brought to you by @andrea.chiarelli

Your feedback and questions are important – feel free to express them!

After <<the configured access token liftime>> minutes, the api-calls fails, with “token expired”.
If i log out and login in the blazor server app, it starts working again.

How can I instead do access-token refresh for active sessions?

It thought this should do it for me automatically:
_httpContextAccessor.HttpContext!.GetTokenAsync("access_token");

But no, that just keeps returning the same (expired) token as long as the user is logged in…

update:

I found the UseRefreshTokens in the docs. Why this isnt true by default, who know?

    
    .WithAccessToken(options =>
    {
        options.Audience = builder.Configuration["Auth0:Audience"];
        options.UseRefreshTokens = true;
    });

The problem is, still not working. Access tokens are not refreshed.

Update 2:
I verified that “use refresh tokens” , “allow offline access” is checkd in the settings.
I have also added the scope “offline_access”.

Still not working.

@robertino.calcaterra please advice.

Hey @sebastian4,
With options.UseRefreshTokens = true; you don’t need to specify the offline_access scope. The SDK does it for you.
As far as I can see, with the setup you described, refresh tokens should work.
Make sure to log out and log in again after you make your settings on your Auth0 dashboard.
This article explains how to use refresh tokens in an ASP.NET Core application and includes a working sample project, but basically it does what you already did.
Have you had a chance to take a look at the HTTP requests exchanged between your application and your Auth0 tenant to see if actually a refresh token is requested and obtained?

Hello,

Thank you very much for the tutorial. I retrieve your project and understand main steps. However, sometimes the first call of the external API returns 404. With the classic yellow message on the bottom of the page. And clicking to reload, everything works. I stop the debug mode of my blazor web app, restart and all seems to work.

I add a polly retry policy but it doesn’t work. If I restart the external API project, same behavior, the first call of the API returns 404 from Blazor project.

Is there any problem with the httpclient or you think it’s the render mode?

Hey @alpachinois,
Thank you for appreciating my tutorial and welcome to the Auth0 Community!

To be honest, I’m not able to reproduce the issue you are experiencing. All my requests to the external API are successful.
I have no evidence to say that it depends on the render mode, although I doubt it, or on the HttpClient.

Are you running the sample project or did you apply the implementation steps to another project?
Have you tried to capture the HTTP requests and debugging the API in the Blazor project?

Hello,

Great tutorial series!

Locally, on my PC, everything works perfectly.
If the API is not started, I get a 404 error.
However, when my API is hosted by Ionos, I systematically get a 404 error.
According to my research, only the site [ASP.NET] Ionos Windows Hosting Proxy for Auth0 attempts to answer it.
Do you have an idea ?

Hello @fabien.lemore,
I’m glad you appreciate these tutorials. Thanks!

I don’t know Ionos infrastructure and constraints. However, from what you are saying (i.e., accessing the external API hosted on Ionos gives you a 404 status code), I don’t think that the document you shared applies. You are just calling an API endpoint. Auth0 is not involved in this step at all.

I suggest the following test: call the remote API endpoint with a browser or with cURL, even without an access token. If you still get a 404 status code, the problem is on the API hosting (no idea about the specific problem). If you get a 401 status code, then the API is hosted correctly and the problem is in your Blazor app.

Make sure you have changed the value of the ExternalApiBaseUrl key in the appsettings.json file. If it’s correct, try to capture the HTTP request sent by your Blazor app to the remote API and see if it’s as expected.

Side note: If the document about the proxy is valid, I’m afraid you need to apply it to the remote API. While the issue you are experiencing right now is not related to this, once you solve it, you’ll find that the API needs to access Auth0 to validate the access token, and it seems that Ionos does not allow outbound calls.

First of all - great post. Thank you very much for sharing a demo of this, particularly against the Blazor Web App template.

This solution is working end-to-end for me, but only if I don’t require authentication on the “External API” project. Otherwise, if I wrap the app.MapGet("/api/externalData") body, I’ll catch an exception indicating a 401. I’ve checked my solution against your post several times and against the GitHub repo, but I’m just not seeing what I’m missing.

On my Server project, I’m setting up the Auth0 values with:

builder.Services.AddAuth0WebAppAuthentication(opt =>
    {
        opt.Domain = builder.Configuration["Auth0:Domain"];
        opt.ClientId = builder.Configuration["Auth0:ClientId"];
        opt.ClientSecret = builder.Configuration["Auth0:ClientSecret"];
    })
    .WithAccessToken(opt =>
    {
        opt.Audience = builder.Configuration["Auth0:Audience"];
    });

Where the values are sourced as follows:
Auth0:Domain - Dashboard > Web app > Settings > Domain
Auth0: ClientId - Dashboard > Web app > Settings > Client ID
Auth0: ClientSecret - Dashboard > Web app > Settings > Client Secret
Auth0:Audience - Dashboard > API app > Settings > Identifier

Per the Github repo, I don’t have any explicit initialization of the values on the external API - just AddAuthentication().AddJwtBearer() and AddAuthorization() during DI registration.

I’ve tried logging out and back in, but I’m stumped. When I attempt to step into the MapGet body to get more information, it immediately throws. Do you have any suggestions for how I might start trying to debug what’s going wrong here? Thank you!

Hi @whit.waldo,
If I correctly understand, you get the 401 status code when you call the /api/externalData endpoint, before it calls the actual external API, right?

If so, this sounds strange :thinking: It looks like you are missing the authentication cookie, but that would throw a 401 even for the /api/internalData endpoint.

Have you tried downloading and running the code from the repo?
Also, the details of the HTTP requests might help to understand what’s happening.

I made a reproducible version of my experience here (but be warned, you need to have Dapr installed and initialized on your machine to run it as it was a proof of concept of Auth0 and other ideas).

It needs the Auth0 values in some envvars: daprpoc_auth0_domain, daprpoc_auth0_audience, daprpoc_auth0_clientid and daprpoc_auth0_clientsecret. If you do launch it, open https://localhost:7084, log in, click the link to the counter page and there are two buttons. Click the Anonymous option and it pulls a value from an external API (Feature API) marked with [AllowAnonymous] and it’ll work fine. Click the Authenticated option and if you dig through the exception log, you’ll see it fails with a 401.

In case you’re not able to run it, the network logs + debug stepthrough show that the request from the Blazor Client app to Server works, from Server to Feature API is what returns the 401. The anonymous route is identical code but with [AllowAnonymous] and it works fine. I went through the demo app and compared the setup but don’t see anything missing. I’d appreciate any other thoughts you have on it.

Unfortuneatly this solution doesn’t really work for Interactive Auto mode, it’s working only because you have already downloaded the wasm resources and are not running on server as per interactive auto mode until the wasm resources have downloaded.

Delete all your applicaiton local storage and change your network settings to fast3g then refresh the page and note the api call doesn’t work and you will get the below error, becuase the code is running server side still.

InvalidOperationException: An invalid request URI was provided. Either the request URI must be an absolute URI or BaseAddress must be set.

Can you please provide a solution that works with Interactive Auto correctly?

Actually if you change to call the api in OnInitializedAsync it won’t work either due to pre-rendering.

e.g.

protected override async Task OnInitializedAsync()
{
    if (authenticationState is not null)
    {
        var state = await authenticationState;

        Username = state?.User?.Identity?.Name ?? string.Empty;

        await CallInternalApi();
    }

    await base.OnInitializedAsync();
}

Hey @whit.waldo, unfortunately I’m not familiar with Dapr, so I’m not sure I can help you effectively.

One thing I noticed, though: you are calling the external API through daprClient.InvokeMethodAsync() here. However, you configured the HttpClient service with the token handler here, as described in my article.
Again, I’m not familiar with Dapr, but I’m afraid that the daprClient is not sending the access token to the external API. What is the relationship between the HttpClient and the daprClient?
I think you should configure the daprClient to include the access token in its requests.

If you can capture the HTTP request sent by the DaprApiPoc application to the FeatureApi application, you can verify if it contains the access token in its Authorization header.

I hope this can help you find the reason for the issue.

Hi @da-zu, I’m not sure I understand your problem, sorry. Can you elaborate?
Have you tried to run the sample project? What is not working for you? Do you have a particular use case? How is it different from the sample project attached to the article?

Thank you :pray:

Well, I was all sorts of confident you’d figured it out because you’re right, I wasn’t passing the access token through the HttpClient used in Dapr. But I’ve corrected for that and… same thing. While I can step through and see the access token get pulled as expected and placed in the authorization header of the request to the external API, this also immediately throws with a 401.

I double checked that everything in the FeaturesApi Program.cs matches my own setup, but still nothing. Is it perhaps something that needs to be configured in Auth0 itself I might have missed?

I’m running the project attached to this article. You are relying on wasm only, but you have interactive auto which means the components will render server side which means your http client won’t have been added to service because the client wasm program would never have ran yet, it will only run once wasm has downloaded and the user refreshes page.

You can simulate the real behaviour of auto and prerender by simply clearing your browser storage, if you are using chrome/edge go to developer tools and tap Storage then Clear all site data. This will simulate first time user of your solution. Also because you are developing all this locally, you must simulate real network connection so under the Network tab in chrome change No throttling to fast 3g or something else.

After you do the above you will have real world use of your proposed solution, so HARD refresh your app in browser and quickly tap the button to call API you will receive an error because you will be running in pre-rendered version, next wait a few seconds and click button again, this time you will get a different error because you are running in interactive server and the wasm hasn’t ran yet. Wait for wasm to download then refresh the page and your solution will now work.

Is it perhaps something that needs to be configured in Auth0 itself I might have missed?

I don’t think so. Once the client receives the access token, Auth0 is not involved anymore. It’s between the client and the API.

The only involvement of Auth0 is when the API gets the public key to validate the access token received from the client, but you should get a different error.

A few things you can try to isolate the problem:

  • What happens when you call directly the API via curl or Postman or another HTTP client (passing the access token too, of course)?
  • Is there any other message associated with the 401 status code, apart from Unauthorized?
  • Try to capture the actual HTTP request sent to the API (not just debugging the code) and analyze the content
  • Check the API audience registered in the Auth0 dashboard and compare it with the values configured in your client and your API. They must match exactly.

Other possible causes of a 401 status code are listed here.
I hope I have given you enough options to find the cause of the problem

Hi @da-zu,
Sorry for the delay.
Unfortunately, as far as I can see, this is a problem with the Interactive Auto mode itself. There is a long thread on the ASP.NET Core project about this, but it does not seem to be fully resolved yet.
Maybe the solution will come with the ability to check the render mode of the current component, but it looks like that this will be available with .NET 9.

I run the code from GitHub successfully and while using Microsoft Edge got this warning message in the browser console. Will this solution still work in future Edge version?

Third-party cookie will be blocked in future Microsoft Edge versions as part of unpartitioned third-party cookie deprecation.

Also has this message:

  1. Migrate entirely to HTTPS to have cookies sent to same-site subresources

Error

  1. A cookie was not sent to an insecure origin from a secure context. Because this cookie would have been sent across schemes on the same site, it was not sent. This behavior enhances the SameSite attribute’s protection of user data from request forgery by network attackers.

Resolve this issue by migrating your site (as defined by the eTLD+1) entirely to HTTPS. It is also recommended to mark the cookie with the Secure attribute if that is not already the case.

  1. AFFECTED RESOURCES
1. 1 cookie

  1. |Name|Domain & Path|

| — | — |
|.AspNetCore.Antiforgery.wqPgKSmMo-Y|localhost/|

2. 1 request

  1. ping?requestUrl=https%3A%2F%2Flocalhost%3A7255%2F&browserName=&userAgent=Mozilla%2F5.0+(Windows+NT+10.0%3B+Win64%3B+x64)+AppleWebKit%2F537.36+(KHTML%2C+like+Gecko)+Chrome%2F126.0.0.0+Safari%2F537.36+Edg%2F126.0.0.0&browserIdKey=window.browserLink.initializationData.browserId&browserId=4cf6-68d7&_=1721757141922
    • Learn more: How Schemeful Same-Site Works