Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net 8 Blazor Web App Identity Framework Help #53732

Closed
1 task done
perjcarlos opened this issue Jan 31, 2024 · 5 comments
Closed
1 task done

.Net 8 Blazor Web App Identity Framework Help #53732

perjcarlos opened this issue Jan 31, 2024 · 5 comments
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved

Comments

@perjcarlos
Copy link

perjcarlos commented Jan 31, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Super quick overview:

I have a Blazor Web App that is single process only hosting UI.
It communicates with a .Net Web Api for all backend services including Authentication.
Api side I have my own Identity Controller that's a mix of old and new endpoints for Identity (Doesn't really matter here because the end result is a properly issued token and that works fine).
Client side I have a customized workflow and implementation of AuthenticationStateProvider to satiate ClaimsPrinciples.

Here's the damn problem. In .Net 7 you could completely opt out of "Authentication" on the Blazor side and only use "Authorization" with .AddAuthorizationCore() then use a custom Auth workflow and AuthenticationStateProvider like I am. Decorate pages with @attribute [Authorize] and bobs your uncle the AuthenticationStateProvider would be invoked to check for a Principle.

In a .Net 8 Web App no matter what I do and this has been since yesterday. I'm being forced to utilize some sort of Authentication on the blazor side to utilize the [Authorize] attribute. It's driving me banana's because my whole Authentication workflow including Hyrdration of ClaimsPrinciple for that StateProvider is fully custom because of the Web Api.

Just for a little more detail the actual error that I get is:

InvalidOperationException: Unable to find the required 'IAuthenticationService' service. Please add all the required services by calling 'IServiceCollection.AddAuthentication' in the application startup code.

Do anyone have any thoughts here?

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

8.0

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Jan 31, 2024
@mkArtakMSFT mkArtakMSFT added area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer Needs: Repro Indicates that the team needs a repro project to continue the investigation on this issue and removed area-blazor Includes: Blazor, Razor Components labels Jan 31, 2024
@ghost
Copy link

ghost commented Jan 31, 2024

Thank you for filing this issue. In order for us to investigate this issue, please provide a minimal repro project that illustrates the problem without unnecessary code. Please share with us in a public GitHub repo because we cannot open ZIP attachments, and don't include any confidential content.

@ghost ghost added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Jan 31, 2024
@perjcarlos
Copy link
Author

@ghost ghost added Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. labels Jan 31, 2024
@sruszczynski
Copy link

Same problem

@halter73
Copy link
Member

Thanks for providing a repro project. If you have a custom AuthenticationStateProvider that works on the server, it's possible to use it in a custom AuthenticationHandler that's then used by the default IAuthenticationService added by AddAuthentication. For example:

public class CustomAuthenticationHandler(AuthenticationStateProvider authStateProvider, IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder)
    : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
{
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var authState = await authStateProvider.GetAuthenticationStateAsync();
        if (authState.User.Identity?.IsAuthenticated == true)
        {
            return AuthenticateResult.Success(new AuthenticationTicket(authState.User, Scheme.Name));
        }
        else
        {
            return AuthenticateResult.NoResult();
        }
    }

    protected override Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        Response.Redirect("/login");
        return Task.CompletedTask;
    }
}

And then you can add it to your server project with the following:

builder.Services.AddAuthentication()
    .AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>("CustomSchemeName", options => { });

This doesn't work in the case of the CustomAuthenticationStateProvider in the YuJu.EESS.Express repro project, because Blazored.SessionStorage relies on JavaScript interop which isn't possible early in the middleware pipeline when the authentication middleware runs.

Another option might be to add .AllowAnonymous() to MapRazorComponents<App>() and rely entirely on the logic in Routes.razor to handle unauthorized requests. This means that <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" /> in Routes.razor needs to be changed to call <AuthorizeRouteView ... instead as follows:

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(YuJu.EESS.Express.Client._Imports).Assembly)
    .AllowAnonymous();
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
    <NotAuthorized>
        @if (context.User.Identity?.IsAuthenticated != true)
        {
            NavigationManager.NavigateTo("login", forceLoad: true);
        }
        else
        {
            <p role="alert">You are not authorized to access this resource.</p>
        }
    </NotAuthorized>
</AuthorizeRouteView>

And then you also need to disable prerendering for the Blazored.SessionStorage-based CustomAuthenticationStateProvider to work since JavaScript interop does not work during prerendering:

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

You might also be able to get new InteractiveAutoRenderMode(prerender: false) and new InteractiveWebAssemblyRenderMode(prerender: false) to work if you move Routes.razor and everything it depends on to the Client project. If you don't, you'll see errors like "Root component type 'YuJu.EESS.Express.Components.Routes' could not be found in the assembly 'YuJu.EESS.Express'" in the browser console.

However, if you want to do authentication completely client side, your best bet is probably to continue using UseBlazorFrameworkFiles(), UseStaticFiles(), and MapFallbackToFile("index.html") like you would prior to .NET 8. This is still fully supported, and I don't think there's much of a downside if your app and authentication logic is purely client-side to begin with.

I think this issue is similar to #52063 in that it involves managing the additional complexity of making Blazor auth work after #49622 unified Blazor's routing with regular ASP.NET Core endpoint routing which relies on middleware for auth rather than the AuthenticationStateProvider.

@halter73 halter73 removed Needs: Repro Indicates that the team needs a repro project to continue the investigation on this issue Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. labels Mar 27, 2024
@halter73 halter73 removed their assignment Mar 27, 2024
@halter73 halter73 added the area-blazor Includes: Blazor, Razor Components label Apr 1, 2024
@MackinnonBuck MackinnonBuck added ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. and removed area-blazor Includes: Blazor, Razor Components labels Apr 2, 2024
Copy link
Contributor

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved
Projects
None yet
Development

No branches or pull requests

6 participants
@halter73 @perjcarlos @MackinnonBuck @mkArtakMSFT @sruszczynski and others