I spent few hours trying to figure out how to add authentication using two Blazor WASM apps. This is a minimal sample of the working solution.
Demo app on azure.
There is also branch (master-two-slashed-apps) with the (easier) solution for /firstapp and /second app.
The master branch has naked app (/) and /second app.
When you separate the apps, your users could face much smaller payloads (the admin section is in another app).
There are multiple tutorials and repos about multiple Blazor WASM apps that run under the same ASP.NET core hosted app:
- Oficial documentation.
- Hydra app. Demo. GitHub. CodeProject.
- Demo App from Javier Calvarro Nelson.
None of these tutorials describes how to use authentication.
-
I used the default Visual Studio template for Blazor WASM ASP.NET core Hosted, with enabled Authentication: .
-
Add second project (Client2 in this case). Use the same template, with individual user accounts. Add a reference to the shared project. Also add reference to server project.
-
Follow the instructions from docs. With some changes:
-
In
index.html
(for both client projects) editbase
tag to<base href="/FirstApp/" />
and<base href="/SecondApp/" />
-
Edit
Startup.cs
, add some configuration (likeUseIdentityServer()
, etc..) -
Edit
Program.cs
ofClient2
. It should look the same as in the first project.public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); builder.Services.AddHttpClient("MultipleBlazorAppsWithAuth.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>(); // Supply HttpClient instances that include access tokens when making requests to the server project builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("MultipleBlazorAppsWithAuth.ServerAPI")); builder.Services.AddApiAuthorization(); await builder.Build().RunAsync(); } }
-
Don't forget to add
Extensions.Http
package inSecondClient
:<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
-
-
In
OidcConfigurationController.cs
(server project) editHttpGet
attributes, add following:[HttpGet("_configuration/{clientId}")] [HttpGet("FirstApp/_configuration/{clientId}")] [HttpGet("SecondApp/_configuration/{clientId}")] public IActionResult GetClientRequestParameters([FromRoute] string clientId) {
-
Add client in
appsettings.json
. IdentityServer section should look like this:"IdentityServer": { "Clients": { "MultipleBlazorAppsWithAuth.Client": { "Profile": "IdentityServerSPA" }, "MultipleBlazorAppsWithAuth.Client2": { "Profile": "IdentityServerSPA" } } }
-
Edit service-worker.published.js (see below)
Unhandled exception rendering component: Could not find 'AuthenticationService.init'
Could not load settings from '_configuration/BlazorApp1.Client'
- 404 (when I forgot to reference the project)
- and many more...
There is an issue with HTTPS and Azure. SO question.
There were no calls to server. Even with forced reload.
This seems like a solution: Add this line to service-worker.published.js
&& !event.request.url.includes('/secondapp');
It becomes:
async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
const shouldServeIndexHtml = event.request.mode === 'navigate'
&& !event.request.url.includes('/connect/')
&& !event.request.url.includes('/Identity/')
&& !event.request.url.includes('/secondapp');
const request = shouldServeIndexHtml ? 'index.html' : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}
return cachedResponse || fetch(event.request);
}
GH issue that helped me with this.
Probably not correct (uff).
That issue is so weird. The only thing that seems to work is to change the request path from favicon (??). I guess it has something to do with auth request and redirecting them back to the caller (which is the first client)