Search code examples
c#asp.net-corereact-reduxsingle-page-applicationidentityserver4

Want to add Hybrid Flow Authentication to the (dotnet) backend of a SPA


I haven't seen any documentation/examples on how to add Authentication to a SPA application, for example when using the template on ASP.NET Core 2.1 for react-redux (that uses the usual create-react-app behind the scenes).

Just to clarify, I am not asking how to add Implicit Flow authentication to the React client app, I do know how to do that, but it's not secure enough for our customers. I want the backend (ASP.NET Core) to handle authentication via an OIDC Identity Provider, using the Hybrid flow.

I am adding the following to the services pipeline:

services.AddAuthentication(o =>
{
    o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o =>;
{
    o.SlidingExpiration = bool.Parse(Configuration["SessionCookieSlidingEnabled"]);
    o.ExpireTimeSpan = TimeSpan.FromMinutes(int.Parse(Configuration["SessionCookieLifeTimeInMinutes"]));
    o.Cookie = new CookieBuilder { HttpOnly = true };
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, o =>
{
    o.Authority = Configuration["Authority"];
    o.ClientId = Configuration["OpenIdConnectOptions:ClientId"];
    o.ClientSecret = Configuration["OpenIdConnectOptions:ClientSecret"];
    o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    o.GetClaimsFromUserInfoEndpoint = Convert.ToBoolean(Configuration["OpenIdConnectOptions:GetClaimsFromUserInfoEndpoint"]);
    o.RequireHttpsMetadata = Convert.ToBoolean(Configuration["OpenIdConnectOptions:RequireHttpsMetadata"]);
    o.ResponseType = Configuration["OpenIdConnectOptions:ResponseType"];
    o.SaveTokens = Convert.ToBoolean(Configuration["OpenIdConnectOptions:SaveTokens"]);
    o.UseTokenLifetime = false;
}

And then the corresponding app.UseAuthentication(); to the configure method.

Additionally, I've (unsuccessfully) tried to configure a global filter on the MVC service, like so:

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

In the past, I would simply append the [Authorize] filter to the home Controller to trigger the flow, but apparently since 2.1 the framework uses an SPA middleware to load from the ClientApp folder as the entry point for my application:

app.UseSpa(spa =>
{
    spa.Options.SourcePath = "ClientApp";
    if (env.IsDevelopment())
    {
        spa.UseReactDevelopmentServer(npmScript: "start");
    }
});

My question is: how do I trigger the authorization process without the controller filter?

I've already created this inquiry against asp.net core docs, I know that this is not an IdentityServer4 issue, but maybe you smart folks can shed some light on the matter.

Thanks in advance.


Solution

  • The universal solution for SPA and any other static middleware is to perform the challenge manually as the following:

    app.Use(async (context, next) =>
            {
                if (!context.User.Identity.IsAuthenticated)
                {
                    await context.ChallengeAsync();
                }
                else
                {
                    await next();
                }
            });
    

    This middleware should be added to the chain before Spa and Mvc.