Can Of Code

Blazor WebAssembly on a different host to the Server

If you open up the Blazor WebAssembly project template it is configured to run both the client and the server sharing the same host localhost:5001 for example. If you want to host your client separately from the server you have to change a few settings on both the client and server which wasn’t clear out the gate, I have created a walkthrough on how I achieved this and hopefully its some use to you too.

Who is this for?

  • Want to have the Blazor WebAssembly client hosted on a different URL say  client.myexample.com and your API on api.myexample.com
  • Those new to Blazor (like myself)

CORS

Because we are accessing the Server from a different host than the API we have to configure CORS to allow access. What is less than ideal is that you do have to set the CORS for IdentityServer and for ASP Core separately.

In the Server project I added the following to the ConfigureServices method in Startup.cs

var _loggerFactory = new LoggerFactory(); 
var cors = new DefaultCorsPolicyService(_loggerFactory.CreateLogger<DefaultCorsPolicyService>()) 
{ 
AllowedOrigins = { "https://client.myexample.com"} 
}; 
services.AddSingleton<ICorsPolicyService>(cors);

This adds our client host to the Allowed origins that is used by IdentityServer. We now need to also allow our client to the ASP Core Allowed Origins. There are multiple ways to do this but I had success using the following in the Configure Method in the servers Startup.cs.

app.UseCors(policy => { 
    policy.AllowAnyHeader(); 
    policy.AllowAnyMethod(); // This was added to stop blocking DELETE requests 
    policy.WithOrigins("https://client.myexample.com"); 
});

This should at least allow us to send requests from our client hosted at client.myexample.com to our server at api.myexample.com

Setting up the OIDC Client

OpenID Connect (OIDC) uses clients to keep track of things like redirect urls allowing you to have a different client for different frontends, say Mobile Apps or SPA. in the Blazor example, these are set in the appsettings.json and the default setup looks like this:

"IdentityServer": { 
"Clients": {
"WhatsForDinDins.Client": { "Profile": "IdentityServerSPA" } }
}

While this keep things nice and tidy for someone new, it does hide the configuration we need to set if we are not based on the same host. After a lot of trial and error I found the following configuration allowed us to authenticate with a client on a different host:

"IdentityServer": {
"Clients": {
"ProjectName.Client": {
"Profile": "SPA",
"RedirectUri": "https://client.myexample.com/authentication/login-callback/",
"LogoutUri": "https://client.myexample.com/authentication/logout-callback/",
"AllowedScopes": [ "ProjectName.ServerAPI", "openid", "profile" ]}
}
},

Here we are setting the clientId as “ProjectName.Client” and telling IdentityServer that it should callback to our client domain after it has authenticated. We also set the allowed scopes with “ProjectName.ServerAPI” being the API. Although I’m not sure this is required. The Profile value I’m still not sure but I found that setting SPA was what worked where as IdentityServerSPA did not. 

At this point our Server should be set to allow authentication and controller requests from our externally hosted client at client.myexample.com

Setting up the Blazor client

First we need to update our HttpClient to use our server host base address instead of the default which assumes the server is located on the same host as the client.

builder.Services.AddScoped<CustomAuthorizationMessageHandler>();

      
builder.Services.AddHttpClient("ProjectName.ServerAPI", client => client.BaseAddress = new Uri("https://api.myexample.com"))
                .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

You may notice that we have a CustomAuthorizationMessageHandler. We have created a class that is used to ensure that the Authorization knows to look for our server instead of assuming its located on the same domain.

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
        public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
            NavigationManager navigationManager)
            : base(provider, navigationManager)
        {
            ConfigureHandler( 
                authorizedUrls: new[] { "https://api.myexample.com" },
                scopes: new[] { "ProjectName.ServerAPI", "openid", "profile" });
        }
    }

Lastly in our Program.cs of the client we need to replace the default API Authentication and replace it with our own configuration values using AddOidcAuthentication:

builder.Services.AddOidcAuthentication(options =>
{
    options.ProviderOptions.MetadataUrl = "https://api.myexample.com/.well-known/openid-configuration";
    options.ProviderOptions.ClientId = "ProjectName.Client";
    options.ProviderOptions.Authority = "https://api.myexample.com/";
    options.ProviderOptions.ResponseType = "code";
    options.ProviderOptions.RedirectUri = "https://client.myexample.com/authentication/login-callback/";
    options.ProviderOptions.PostLogoutRedirectUri = "https://client.myexample.com/authentication/logout-callback/";
    options.UserOptions.ScopeClaim = "ProjectName.ServerAPI openid profile";
 });

And with that we should be set up to authenticate and call authorized controller actions from a client hosted on a separate host from its server.

It might not be the best way..

I came to this solution as a beginner to Blazor and with a rough understanding of OIDC so there maybe a better way to configure this without having to change so much configuration. Perhaps you only have to set the CORS in one location? Happy to hear if you have a different solution and of course I will update this post if I find out more. 

Posted in Blazor | Tagged , |