Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I attempted to build this application myself but, have hit several stumbling blocks along the way. I am thinking that it may be best to step back and take a larger look at what I am trying to create. There doesn't seem to be any documentation on how to make what I am looking for. (unless someone can point me in the right place I might have missed)

Ultimately what I would like is to have a Blazor(server-side) application make API calls to use data in the app and then have an IdentityServer4 encapsulate the authentication. I need to have Azure as well as ASP.net Identity as the possible authentication methods.

I have tried and was able to create an IdentityServer4 that also has a local API. I can make calls to this from Postman to get token and such. But, when it comes to tying a Blazor(server-side) application to the IdentityServer4 I am befuddled.

I have tried to ask this question in specifics but, haven't gotten any results at all. I am hoping maybe this larger look at it might be helpful.

It seems like odic-client.js is the way to get the data from the IdentityServer4 callback but, that doesn't seem to tie in nicely with the .NET Authorization in Blazor(server-side). How do I get these to work together.

like image 692
Rabbitslayer Avatar asked Jul 23 '19 22:07

Rabbitslayer


People also ask

How do I add authentication to Blazor server app?

Creating a Blazor application with AuthenticationWhen you hit the project type screen, select Blazor Server App then select the Change link under Authentication. From the popup window select Individual User Accounts and then OK. Make sure that Authentication is set to Individual User Accounts then click Create.

What is Blazor API?

Blazor WebAssembly apps call web APIs using a preconfigured HttpClient service, which is focused on making requests back to the server of origin. Additional HttpClient service configurations for other web APIs can be created in developer code. Requests are composed using Blazor JSON helpers or with HttpRequestMessage.

What is an IdentityServer?

IdentityServer is an authentication server that implements OpenID Connect (OIDC) and OAuth 2.0 standards for ASP.NET Core. It's designed to provide a common way to authenticate requests to all of your applications, whether they're web, native, mobile, or API endpoints.


1 Answers

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/

like image 171
Pascal R. Avatar answered Sep 28 '22 01:09

Pascal R.