locked
Provided token was meant for different claims-based user. 400 bad request error in WebAPI Asp.Net core 3.1 project. RRS feed

  • Question

  • User613219449 posted

    I tried to create a defense against CSRF attacks based on Microsoft's tutorial. Since I have a Web API project, I use JWT authentication, dividing the level of user access based on their role. This way, I have an error when I try to access a secure controller:

    Antiforgery token validation failed. The provided antiforgery token was meant for a different claims-based user than the current user. Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The provided antiforgery token was meant for a different claims-based user than the current user. at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet) at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext) at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)

    and 400 Bad Request in Postman where I test my API.

    My sample controller:

    [Authorize(AuthenticationSchemes = "Bearer", Roles = "Admin")]
    [ValidateAntiForgeryToken]
    [HttpPost(Name = "CreatePost")]
    public async Task<ActionResult<Unit>> Post([FromBody] CreatePost.Command command)
    {
        return await _mediator.Send(command);
    }

    My Startup class:

    public class Startup
    {
        public IConfigurationRoot Configuration { get; private set; }
        public Startup(IWebHostEnvironment env, IConfiguration configuration)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            if (env.IsDevelopment())
            {
                builder.AddUserSecrets<Program>();
            }
            Configuration = builder.Build();
        }
    
        public ILifetimeScope AutofacContainer { get; private set; }
    
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions();
    
            // Add Authentication and configure it
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["TokenKey"]));
    
            var jwtAppSettingsOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
            services.Configure<JwtIssuerOptions>(options =>
            {
                options.Issuer = jwtAppSettingsOptions[nameof(JwtIssuerOptions.Issuer)];
                options.Audience = jwtAppSettingsOptions[nameof(JwtIssuerOptions.Audience)];
                options.SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            });
    
            services.AddAuthentication(opt =>
            {
                opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                opt.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
                opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    
            })
            .AddJwtBearer(opt =>
            {
                opt.RequireHttpsMetadata = false;
                opt.SaveToken = true;
                opt.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
                {
                    LifetimeValidator = (before, expires, token, param) =>
                    {
                        return expires > DateTime.UtcNow;
                    },
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = key,
                    ValidateAudience = false,
                    ValidateIssuer = false,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.Zero
                };
                opt.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        var accessToken = context.Request.Query["access_token"];
    
                        var path = context.HttpContext.Request.Path;
                        if (!string.IsNullOrEmpty(accessToken) &&
                            (path.StartsWithSegments("/hubs/chat")))
                        {
                            context.Token = accessToken;
                        }
                        return Task.CompletedTask;
                    }
                };
            });
    
            services.AddAuthorization();
    
            // Register SignalR
            services.AddSignalR();
    
            // Data protection
            services.AddDataProtection()
                .PersistKeysToDbContext<KeysDataContext>();
    
            // Add asp.net identity
            services.AddIdentity<AppUser, IdentityRole>((options) => {
                options.User.RequireUniqueEmail = true;
            })
            .AddEntityFrameworkStores<DataContext>()
            .AddDefaultTokenProviders();
    
            // Configure asp.net identity
            services.Configure<IdentityOptions>(opt =>
            {
                opt.Password.RequireDigit = true;
                opt.Password.RequiredLength = 6;
                opt.Password.RequireNonAlphanumeric = false;
                opt.Password.RequireUppercase = true;
                opt.Password.RequireLowercase = false;
            });
    
            // Add fluent validation
            services.AddControllers()
                .AddFluentValidation(cfg =>
                {
                    cfg.RegisterValidatorsFromAssemblyContaining<CreatePost>();
                });
    
            // CORS policy
            services.AddCors(opt =>
            {
                opt.AddPolicy("CorsPolicy", policy =>
                {
                    policy.WithOrigins("https://localhost:5000/")
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials();
                });
            });
    
            services.AddAntiforgery(options => {
                options.HeaderName = "X-XSRF-TOKEN";
                options.SuppressXFrameOptionsHeader = false;
            });
    
            // Swagger config
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
                c.CustomSchemaIds(type =>
                {
                    if (type.FullName.EndsWith("+Command") || type.FullName.EndsWith("+Query"))
                    {
                        var parentTypeName = type.FullName.Substring(type.FullName.LastIndexOf(".", StringComparison.Ordinal) + 1);
                        return parentTypeName.Replace("+Command", "Command").Replace("+Query", "Query");
                    }
    
                    return type.Name;
                });
            });
    
            services.AddMvc();
        }
        
        public void ConfigureContainer(ContainerBuilder builder)
        {
            // Register custom autofac modules
            builder.RegisterModule(new StartupModule());
            builder.RegisterModule(new DbContextModule());
            builder.RegisterModule(new MediatrModule());
            builder.RegisterModule(new SecureDataContextModule());
            builder.RegisterModule(new AutoMapperModule());
        }
    
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAntiforgery antiforgery)
        {
            // Add custom error hadling with http requests
            app.UseMiddleware<ErrorHandlingMiddleware>();
    
            app.UseHttpsRedirection();
            app.UseHsts();
    
            // Using development tools
            if (env.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1"); });
            }
    
            app.UseRouting();
    
            // Implement CORS policy
            app.UseCors("CorsPolicy");
    
            app.UseAuthentication();
            app.Use(next => context =>
            {
                
                var tokens = antiforgery.GetAndStoreTokens(context);
                context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
                    new CookieOptions() { HttpOnly = false });
    
                return next(context);
            });
            app.UseAuthorization();
    
            // Add serilog middleware with http context enricher
            app.UseMiddleware<SerilogMiddleware>();
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapHub<GroupChatHub>("/hubs/chat");
            });
        }
    }

    and decoded JWT:

    Decoded JWT

    Tuesday, June 30, 2020 9:17 AM

Answers

  • User475983607 posted

    The standard anti-forgery token is used in browser based applications and designed to protect MVC Controllers not Web API.   Web API uses CORS to handle cross origin requests from browsers.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, June 30, 2020 10:32 AM