简体   繁体   中英

Having issues with IAntiforgery in ASP.NET Core 6.0

Please look at my long answer at the end about how I resolved this. I had gotten too frustrated and after another day with a fresh perspective and more sleep, I got to a solution.

I did this in 5.0 with no issues in the Startup.Configure method.

Basically I created a header for the request on a protected route. I'm using React as the front end. I'm finding when I place everything in Program.cs the dependency injection, authorization doesn't work right so I split up into separate Program and Startup files.

But I can't use the following signature in 6.0 like I did in 5.0:

example that worked in 5.0:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAntiforgery antiforgery)
{
    app.UseEndpoints(endpoints =>            
        {
            endpoints.MapGet("antiforgery/token", context =>
            {
                var tokens = antiforgery.GetAndStoreTokens(context);
                context.Response.Headers.Append("XYZ", tokens.RequestToken!);                    
                return Task.FromResult(StatusCodes.Status200OK);
            });                
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller}/{action=Index}/{id?}");
        });
}

Program.cs (my attempt to split up program and startup - 6.0)

var startup = new dolpassword.Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);

var app = builder.Build();

startup.Configure(app,app.Environment);

Saw this example on Microsoft website:

app.UseRouting();

app.UseAuthorization();
// app.Services syntax error in Configure for 6.0
var antiforgery = **app.Services.GetRequiredService<IAntiforgery>();**

 app.Use((context, next) =>
 {
      var requestPath = context.Request.Path.Value;

      if (string.Equals(requestPath, "/", 
            StringComparison.OrdinalIgnoreCase)
            || string.Equals(requestPath, "/index.html", 
              StringComparison.OrdinalIgnoreCase))
        {
            var tokenSet = antiforgery.GetAndStoreTokens(context);
             context.Response.Cookies.Append("XSRF-TOKEN", 
              tokenSet.RequestToken!,
        new CookieOptions { HttpOnly = false });
         }

         return next(context);
   });

I was able to successfully do this in 6.0 so I will share some of the code and how I resolved it. I also had Windows authentication baked in with a policy-based authorization. The reason I'm putting all the authentication/authorization wireup in this post is because the entire solution relies on authentication, authorization and antiforgery.

First I set up my services. I get IAntiforgery by default by adding ControllersWithViews but I want to use my own header name, which is X-XSRF-TOKEN instead of the.AspNet.Antiforgery.xxxx or whatever the default is. I also needed options.DefaultAuthenticateScheme = NegotiateDefaults.AuthenticationScheme; to get Windows auth working.

string CorsPolicy = "CorsPolicy";
//===================================formerly Configure Services
WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
ConfigurationManager _configuration = builder.Configuration;
// Add services to the container.
**IServiceCollection? services = builder.Services;
services.AddAntiforgery(options => { options.HeaderName = "X-XSRF-TOKEN"; 
options.Cookie.HttpOnly = false; });**
services.AddTransient<IActiveDirectoryUserService, ActiveDirectoryUserService>();
services.AddControllersWithViews();
services.AddAuthentication(options => {//needed for Windows authentication
    options.DefaultAuthenticateScheme = NegotiateDefaults.AuthenticationScheme;
});

Adding more... I'm using Windows auth so I'm using the Negotiate provider. Then I set up my Authorization. I insert my own authorization policy and also add my claims transformers to get the authenticated user into a claim. The fallback policy in Authorization was causing an Authentication exception.

services.AddAuthorization(options =>
{
    // options.FallbackPolicy = options.DefaultPolicy;//authorization bombs if you include this line
    options.AddPolicy("AuthenticatedOnly", policy => {
        policy.Requirements.Add(new AuthenticatedRequirement(true));
    });    
});
services.AddTransient<IClaimsTransformation, MyClaimsTransformer>();            
services.AddTransient<IAuthorizationHandler, AppUserRoleHandler>();
services.AddTransient<IAuthorizationHandler, AuthenticatedRoleHandler>();
services.AddCors(options =>
{
    options.AddPolicy(CorsPolicy,
    builder => builder
        .WithOrigins("https://localhost:7021","https://localhost:44414") 
         //Note:  The URL must be specified without a trailing slash (/).
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials());
 });

Now I'm in the middleware territory...as you know, order matters in your middleware. In 5.0 you could add IAntiforgery to your constructor and DI would handle the rest. In program.cs you don't have that luxury. Fortunately you can just grab it out of your services collection and you see that in the following code.

//==============formerly Startup.Configure=====================
WebApplication app = builder.Build();

app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseCors(CorsPolicy);

IAntiforgery? antiforgery = app.Services.GetRequiredService<IAntiforgery>();

Now when I'm setting up my endpoint routing. Found out that UseRouting and Use.Endpoints are married at the hip and need to be paired.

I also create a protected route "/auth" (protected by my authorization policy) to grab the antiforgery request token generated when we added it in the services collection. So this header won't be persisted from request to request like a cookie would. The minimal API allows me to create a route without creating the controller and action in a separate controller class.

app.UseEndpoints(endpoints =>          
{
    endpoints.MapGet("/auth", context =>
    {        
        var tokens = antiforgery.GetAndStoreTokens(context);
        context.Response.Headers.Append("XYZ", tokens.RequestToken!);       
        return Task.FromResult(Results.Ok());
    }).RequireAuthorization("AuthenticatedOnly");

    endpoints.MapControllerRoute(
        name: "default"
        pattern: "{controller}/{action=Index}/{id?}");
});

My React front end will use a fetch get request to get the token from the headers collection and then stick into a second post request and voila it works.

BTW, React doesn't provide Antiforgery functionality out of the box like Angular, in step with it's minimalist API ethos.

The action I'm posting to looks like this:

[HttpPost]
[Authorize(Policy="AuthenticatedOnly")]
[ValidateAntiForgeryToken]
public string Update()

I fully realize there are other ways to do this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM