Securing Blazor WebAssembly Apps

Been battling with Blazor for a while now. Finally getting around to securing a site with Auth0.
All goes well, can login and get the UI to change accordingly.

ā€¦ butā€¦

The logout process is a bit strange. Tried a number of approaches but always get the ā€œlogout-failedā€ call-back. Most recently with the error ā€œThe logout was not initiated from within the page.ā€

Really need to understand those final vital steps of what is the correct approach for logging out of Auth0 also.

1 Like

Hi @paulkiddybytes,
The error you are reporting is strange. I know there are still issues with the logout process in Blazor WASM, as mentioned in the article and as reported by a few readers in the comment above.
However, they donā€™t report any failure.
Can you give me more information about your context (e.g. requests and responses affected by the error)?
Also, keep in mind that the article targets .NET 3.1. It has not been tested on .NET 5.

If you want to learn more on the logout process in Auth0, please, check out this document.

Hi, great article!

Iā€™ve just followed the tutorial and successfully integrated Auth0 into my new Blazor app. Also, I added an Email Domain Whitelist rule which seems to work as expected, however, whenever the rule fails it always just redirects to the following url: "/authentication/login-failed?message=There%20was%20an%20error%20signing%20in. I need to know whether the rule failed due to an invalid domain or the just that email has not been verified. Do you know if there is a way to get the callback to tell me the reason for the failure? Also, do you know where is the message ā€œThere was an error signing inā€ coming from?

1 Like

Hi Paul,
Thank you for appreciating my article :slight_smile:

Regarding your request, does your rule throw an exception as in this example?
As far as I know, the error message should be returned to the client.

ā€œThere was an error signing inā€ message is the builtin error message from Blazor WASM.

Did you ever solve this @paulkiddybytes ? I am getting the same error.
.cc @andrea.chiarelli

1 Like

Hi folks!
Thx for this manual.
I faced one problem that was described here: [Blazor][Wasm] Set oidc Authentication Options in local storage Ā· Issue #20574 Ā· dotnet/aspnetcore Ā· GitHub
The problem is that the oidc response is saved in the session storage by default and I havenā€™t find a way to configure it. I have to authenticate every time I open a new tab.
Did smbd face this issue too?

Hi @itbeard,
Welcome to the Auth0 Community.

As mentioned in the issue thread, storing the ID token in the local storage is not secure. Actually, even storing it in the session storage is not so secure.
You can learn more on token storage on the browser by reading the following docs:

Regarding the specific issue, it seems they solved the issue with a silent authentication approach. Did you try with an updated version of Blazor?

Hello!
Thx for your reply.
I use the last version of Blazor that included in .NET5.
Authentication works while I refresh the page, but non when I one page in the new tab, unfortunately.
Looks like the hidden iframe approach does not work in my case for some reason ā€¦

Nope. Iā€™m stuck with the process of logging out. Iā€™ve tried all kinds of stuff. The logout URL doesnā€™t return properly and the browser seems to cache the logged in user. Simply no way to properly and reliably logout a user. Doesnā€™t help that the whole stack seems really buggy.

Any luck in this topic? I have the same issue

I am using this example, my code below.

The token.Value is very small, like it is just an token code. How do I get an access token that includes ā€œaudā€,ā€œissā€, etc?

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {

        try
        {
            var accessToken = await this._tokenProvider.RequestAccessToken(new AccessTokenRequestOptions());
            if (accessToken.TryGetToken(out AccessToken token))
            {
                request.Headers.Add("Authorization", $"Bearer {token.Value}");
                return await base.SendAsync(request, cancellationToken);

            }
            else
            {
                throw new InvalidOperationException("Unable to get token.");

            }

        }
        catch (System.Exception e)
        {
            Console.WriteLine(e.ToString());
            throw;
        }
    }

I can see the calls in fiddler to:
https://tjb.auth0.com/authorize?client_id=XXX
https://tjb.auth0.com/oauth/token
https://tjb.auth0.com/userinfo

and they are all successfulā€¦

and the https://tjb.auth0.com/oauth/token response includes a json payload with the access_token and the id_token. I need the value of id_token. How do I get the value of id_token?

Hey folks, this blog post has been revamped to .NET 5!

3 Likes

Woooohoo great news!

Hi @Tim_Bassett, Did you get the solution for that? I am also facing the same issue.
I have configured everything to make HTTP requests with auth token but my API calls were failing due to an invalid token. access_token is attaching to the requests instead of id_token. id_token is actual JWT.

We ended up getting rid of all the manual manipulation of the Token by using the BaseAddressAuthorizationMessageHandler. We also had CORS issues, so inevitably getting everything onto the same domain (both the web and the api) got get rid of the CORS issues, which yielded the opportunity to just use the BaseAddressAuthorizationMessageHandler. Using the BaseAddressAuthorizationMessageHandler got us out of the manually handling the token business, and then everything just worked.

We do use the AuthorizationMessageHandler to do a bit of manual token handling for development against the localhost api (different port). I believe using the AddHttpClient along with the other pattern inside the delegate got us home on that frontā€¦

                        builder.Services.AddHttpClient<TClient>(client => builder.Configuration.Bind("HttpClient", client))
                        .AddHttpMessageHandler<AuthorizationMessageHandler>();
                    builder.Services.AddTransient<AuthorizationMessageHandler>(sp =>
                    {
                        // šŸ‘‡ Get required services from DI.
                        var provider = sp.GetRequiredService<IAccessTokenProvider>();
                        var naviManager = sp.GetRequiredService<NavigationManager>();

                        // šŸ‘‡ Create a new "AuthorizationMessageHandler" instance,
                        //    and return it after configuring it.
                        var handler = new AuthorizationMessageHandler(provider, naviManager);
                        handler.ConfigureHandler(authorizedUrls: new[] {builder.Configuration["HttpClient:BaseAddress"]});
                        return handler;
                    });

I am not sure whether it is an audience issue or some other issue. Because I have verified the login call back URL and noticed both the tokens (access_token and id_token) are coming but only access token mapping to HTTP requests.
image

Hi @Tim_Bassett, I have tried with your code but no luck. Still access_token passing as Bearer token in request headers.

image

Sorry, I didnā€™t fully comprehend what you were trying to accomplishā€¦

In our backend (asp.net) we are using middleware to get the token from the access token.

Some of this is voodoo to me, but I believe the ā€œspellā€ is in this:

            string domain = $"https://{this.Configuration["Auth0:Domain"]}/";
        services
            .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.Authority = domain;
                options.Audience = this.Configuration["Auth0:Audience"];
                // If the access token does not have a `sub` claim, `User.Identity.Name` will be `null`. Map it to a different claim by setting the NameClaimType below.
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = ClaimTypes.NameIdentifier
                };
            });