How do you create a API/IdentityServer/Blazor(server-side) application?

IMPORTANT: There are better sources now than my answer. Follow the links provided in the last part of this answer.

I've got a similar setup with API / IdentityServer4 / Blazor(server-side). I'll show you some of the code I used, maybe you can make some use of it.

Using the NuGet Package Microsoft.AspNetCore.Authentication.OpenIdConnect, I've got this code in the ConfigureServices method in the Startup class:

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.Authority = "https://localhost:5001";

            options.ClientId = "myClient";
            options.ClientSecret = "mySecret";
            options.ResponseType = "code id_token";

            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;

            options.Scope.Add("MyApi");
            options.Scope.Add("offline_access");

            options.ClaimActions.MapJsonKey("website", "website");
        });

and in the Configure method app.UseAuthentication();

Then in App.razor i used the CascadingAuthenticationState component:

<CascadingAuthenticationState>
     <Router AppAssembly="typeof(Startup).Assembly" />
</CascadingAuthenticationState>

And using the NuGet package Microsoft.AspNetCore.Authorization in my main page Index.razor:

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

Now it should say "Not authenticated" when you open the main page but there's still no redirection to the IdentityServer4. For this you've got to add MVC in the startup too, as I learned from this stackoverflow question:

        services.AddMvcCore(options =>
        {
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        });

Now you should be getting redirected to IdentityServer4 to log in after starting the application. In my case I've got an ApiClient, which describes the methods of my API. I use DI to inject the ApiClient and add the access token:

        services.AddHttpClient<IApiClient, ApiClient>(async (serviceProvider, client) =>
        {
            var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();

            var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

            client.BaseAddress = new Uri("http://localhost:55578");
        });

Like you said, there is not much documentation on this topic except some answers here on stackoverflow. It took me a long time to set this up, so I hope I can help someone else with this post.

UPDATE: Logout process

Logging out with this setup requires a detour to a razor page because the HttpContext is inaccessible after the blazor component is loaded.

Create a new Razor Page in the Pages folder and add the following code to the newly created Logout.cshtml.cs:

public class LogoutModel : PageModel
{
    public async void OnGetAsync()
    {
        await HttpContext.SignOutAsync("Cookies");
        var prop = new AuthenticationProperties()
        {
            RedirectUri = "http://localhost:62909"
        };
        await HttpContext.SignOutAsync("oidc", prop);
    }
}

Add a logout button somewhere which calls the function UriHelper.NavigateTo("/Logout") relying on @inject IUriHelper UriHelper. Done!

UPDATE: Login Workaround

The previously described login process worked locally but after publishing to the test server, I had the problem, that the IHttpContextAccessor was always null inside the AddHttpClient method. So I ended up using the same workaround as with the logout process. I let the IdentityServer redirect to a razor page (which always has a HttpContext), save the access token in the user claim and redirect to the index page. In the AddHttpClient method I only get the token from the user claim and put it into the authentication header.

UPDATE: Open issues

I still struggle to get this setup working on our server. I opened this issue and requirement on the AspNetCore Github but both got closed without a proper answer. For the time being, I found some blogs that give a good overview of the current state of the topic: https://mcguirev10.com/2019/12/15/blazor-authentication-with-openid-connect.html https://wellsb.com/csharp/aspnet/blazor-consume-identityserver4-protected-api/