Applying Policy to only one Authentication Scheme .NET 6

Moinuddin Mohamed 46 Reputation points
2022-12-20T11:50:58.96+00:00

Hi,

So, I have a .NET 6 Razor Pages application and within the app, there are two authentication schemes:

  1. Custom authentication (Cookie) - uses a standard forms login and checks against a database.
  2. Azure AD authentication (OIDC) - uses an app registration.

For the Azure AD authentication, I need to be able to check if the user logging in is within a specific group. I have added an authorisation requirement/handler for this and the actual check works perfectly.

I also need to be able to apply both authentication schemes to each of the required pages, but users should only be able to log in using one scheme.

However, when doing this, the policy then gets checked against for both authentication schemes. So, when the user logs in using the forms login (custom authentication), then the policy gets checked when it shouldn't.

The code I have in startup is as follows:

            services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)  
                .AddMicrosoftIdentityWebApp(configuration.GetSection("AzureAd"))  
                .EnableTokenAcquisitionToCallDownstreamApi(scopes)  
                .AddDownstreamWebApi("ApiConfig", configuration.GetSection("ApiConfig"))  
                .AddInMemoryTokenCaches();  
  
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)  
                    .AddCookie("Custom");  
  
            services.AddAuthorization(options =>  
            {  
                options.AddPolicy("ApplicationAccess", policy =>  
                {  
                    policy.AuthenticationSchemes.Add(OpenIdConnectDefaults.AuthenticationScheme);  
  
                    policy.Requirements.Add(new IsStudentRequirement());  
                });  
            });  
  
            services.AddScoped<IAuthorizationHandler, IsStudentHandler>();  

I have also tried the following different uses of the [Authorize] attribute, none of which seem to fulfil this requirement:

  1. Try 1:
    [Authorize]  
    
  2. Try 2:
    [Authorize(Policy = "ApplicationAccess")]  
    
  3. Try 3:
    [Authorize(AuthenticationSchemes = $"{OpenIdConnectDefaults.AuthenticationScheme},{CookieAuthenticationDefaults.AuthenticationScheme}")]  
    
  4. Try 4:
    [Authorize(AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme, Policy = "ApplicationAccess")]  
    
    [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]

I've also tried different options within the services.AddAuthorization(...) method in Startup, including setting a default policy, but this did not work either.

Any help with this will be much appreciated.

Thanks

Kind Regards

Moin

Developer technologies | ASP.NET | ASP.NET Core
Microsoft Security | Microsoft Entra | Microsoft Entra ID
Developer technologies | C#
0 comments No comments
{count} votes

Accepted answer
  1. Anonymous
    2022-12-21T03:14:54.76+00:00

    Hi @Moinuddin Mohamed ,

    The issue is when logging in using the different authentication methods and the policy being checked for both when it should only be checked for one.

    You can try to use AddPolicyScheme() and ForwardDefaultSelector property to set the authentication scheme. Code like this:

    builder.Services.AddAuthentication(options => // JwtBearerDefaults.AuthenticationScheme)  
        {  
            options.DefaultScheme = "JWT_OR_COOKIE";  
            options.DefaultChallengeScheme = "JWT_OR_COOKIE";  
        })  
        .AddCookie(options =>  
        {  
            options.LoginPath = "/login";  
            options.ExpireTimeSpan = TimeSpan.FromDays(1);  
        })  
        .AddJwtBearer(options =>  
        {  
            options.TokenValidationParameters = new TokenValidationParameters  
            {  
                ValidateIssuer = true,  
                ValidIssuer = "issuer",  
                ValidateAudience = true,  
                ValidAudience = "audience",  
                ValidateIssuerSigningKey = true,  
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("signingkey"))  
            };  
        })  
        .AddPolicyScheme("JWT_OR_COOKIE", "JWT_OR_COOKIE", options =>  
        {  
            options.ForwardDefaultSelector = context =>  
            {  
                string authorization = context.Request.Headers[HeaderNames.Authorization];  
                if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))  
                {  
                    return JwtBearerDefaults.AuthenticationScheme;  
                }  
      
                return CookieAuthenticationDefaults.AuthenticationScheme;  
            };  
        });  
    

    Using the above code, the policy will run the JWT_OR_COOKIE scheme, and the JWT_OR_COOKIE scheme will forward select to the actual scheme based on the ForwardDefaultSelector delegate.

    More detail information, refer to Combining Bearer Token and Cookie Authentication in ASP.NET and Multiple authentication schemes

    Besides, I also find the following methods which might achieve the same behavior:

    1. Write a custom middleware that manually calls AuthenticateAsync() and creates a ClaimsPrincipal containing all the identities you need. See How do I setup multiple auth schemes in ASP.NET Core 2.0?
    2. Create a custom Authorization attribute, then check the authorization header (use cookie or token). Refer to Authorization mechanism which uses JWT token OR API-key in .NET Core

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,
    Dillion

    1 person found this answer helpful.

2 additional answers

Sort by: Most helpful
  1. P a u l 10,761 Reputation points
    2022-12-20T13:15:43.213+00:00

    This is just a guess but I've never seen any examples that call AddAuthentication twice, so I'm wondering if that might be causing a conflict somewhere. If you try moving your .AddCookie("Custom") so it chains off the previous AddAuthentication declaration does that change the behaviour?

    0 comments No comments

  2. Moinuddin Mohamed 46 Reputation points
    2022-12-20T13:50:25.877+00:00

    @P a u l , thanks for the quick response.

    I've tried what you have suggested, and it behaves even more strangely - it causes the app to just redirect to the Microsoft Signout page on startup.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.