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

2

6

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.

Geronimo answered 23/7, 2019 at 22:8 Comment(0)
M
17

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/

Marienbad answered 24/7, 2019 at 6:32 Comment(13)
Wow this code is amazing to finally have! I have been working towards this solution for about as long as your original post. It is awesome to finally have something that works for what I am trying to do. I hope that others find this post and use it as a solution!!Geronimo
How do you add a logout to this setup?Geronimo
I haven't added a logout yet but I plan on doing this within the next couple of days. I'll let you know when I get it done.Marienbad
Any luck on the logout process?Geronimo
Yes and I added it to my existing answer. Credits to user robert-swilley who solved my problem in this thread: #57250292Marienbad
this might be a silly followup but, I can't get the Azure setup to logout. I had set it to remember me when logging in the first time but, upon logout the cookies disappear but, Azure remembers that I wanted to stay logged in. How would I force Azure to log out? Or is this something that isn't possible because Azure doesn't implement this?Geronimo
I'm not familiar with Azure authentication but I assume it should work the same way as in Razor Pages which should have enough documentation. With my limited knowledge and a short google, I'd say you need to call a logout url with the format https://login.windows.net/<tenant_id_of_your_app>/oauth2/logout?post_logout_redirect_uri=<logout_URL_of_your_app>/logout which you have to enable in Azure like this: https://mcmap.net/q/735662/-azure-ad-logout-url-not-redirectingMarienbad
I have a similar solution, but my factory for httpclient is far more big to handle refresh-token. However, after retrieving a new refresh-token, I don't know how to save it effeciently as cookie. I'll need to do it the redux way, best solution found so farSteffen
I've added a Logout.cshtml razor page but it seems it could not find @page and @model directives. The name 'page' does not exists in current context. The name 'model' does not exists in current context I am using 3.1-preview3Grandmotherly
@Grandmotherly you should ask that in a new stackoverflow question since it doesn't concern the topic of this thread. Preferably with more information like how you created the page and what you've tried.Marienbad
I've fixed it by adding <AddRazorSupportForMvc>true</AddRazorSupportForMvc> in the .csprojGrandmotherly
Another issue to be aware of is that any expiration applied to the login will be ignored once Blazor switches over to the SignalR connections. The Blazor authentication story is in a pretty sad state right now, unfortunately. And I'm really annoyed the github bot closed the issue started by @PascalR.Microgamete
@PascalR.Did you ever find a good solution for this? I am still struggling with this.Crampton
L
0

Try this
Blazor Consume IdentityServer4 Protected API

Lazar answered 29/1, 2021 at 19:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.