I have a very simple API created using ASP.NET Core 2.1. When a client page calls the API from localhost:8080, the API located at localhost:5000 returns HTTP:405.
Why? Note that the HTTP GET test method in AuthController.cs works just as expected. It is only the HTTP POST request returning HTTP:405.
ControllersAuthController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using server.Models;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.Text;
namespace server.Controllers
{
[Route("api/auth")]
[ApiController]
public class AuthController : ControllerBase
{
[HttpGet, Route("test")]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
[HttpPost, Route("login")]
public IActionResult Login([FromBody]LoginModel user)
{
if (user == null)
{
return BadRequest("Invalid client request");
}
if (user.UserName == "johndoe" && user.Password == "def@123")
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey@345"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken(
issuer: "http://localhost:5000",
audience: "http://localhost:5000",
claims: new List<Claim>(),
expires: DateTime.Now.AddMinutes(10),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return Ok(new { Token = tokenString });
}
else
{
return Unauthorized();
}
}
}
}
ModelsLoginModel.cs
namespace server.Models
{
public class LoginModel
{
public string UserName { get; set;}
public string Password { get; set; }
}
}
Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Cors.Infrastructure;
namespace server
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "http://localhost:5000",
ValidAudience = "http://localhost:5000",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey@345"))
};
});
var corsBuilder = new CorsPolicyBuilder();
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.WithOrigins("http://localhost:8080");
corsBuilder.AllowCredentials();
services.AddCors(options =>
{
options.AddPolicy("SiteCorsPolicy", corsBuilder.Build());
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseCors("SiteCorsPolicy");
app.UseAuthentication();
app.UseMvc();
}
}
}
index.html (localhost:8080)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JWT Auth</title>
</head>
<body>
<p>Output:</p>
<div id="output"></div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
var item = {
UserName: "johndoe",
Password: "def@123"
};
$.ajax({
type: "POST",
accepts: "application/json",
url: '/api/auth/Login',
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
console.log('Testing');
}
});
</script>
</body>
</html>
A reader of my recent ASP.NET Core and Angular 2 book wrote me about a strange error occurred while publishing the OpenGameList Single-Page Application into production upon a Windows Server + IIS powered environment machine.
The published SPA seemed to be working OK at first, but when he logged in as Admin to update an item the command kept failing with the following message:
Error 405 — Methods not Allowed
I did my best to help him to troubleshoot the issue: after a few tests we found out that such error was coming out in response to any attempted PUT and DELETE request, while GET and POST methods were working fine.
Such weird discovery led me to dig through the web looking for a suitable explanation, until I eventually found the cause: it seems like the culprit is the WebDAVModule, which seems to set PUT and DELETE request methods disabled by default. In order to get them to work, we either need to change these defaults or disable it for the whole web application, which was what we did.
Here’s what we put in the web.config file to remove it for good:
<system.webServer> <modules runAllManagedModulesForAllRequests=«false»> <remove name=«WebDAVModule» /> </modules> </system.webServer> |
Despite the rather easy workaround, such an issue is definitely a though one, as it will easily affect most ASP.NET Core Web API and Web Applications when they get deployed on a live environment: that’s because the WebDAV module, although not supported by IIS Express, happens to be enabled in most production servers. I guess that a lot of users will stumble upon it sooner or later… I definitely hope that this post will help some of them to overcome the hassle.
Reason:
The WebDAVModule
set PUT and DELETE request methods disabled by default and due to that PUT and DELETE throw 405 errors.
Solution:
To make the PUT and DELETE requests work, we need to override the WebDAVModule
setting in web.config
file by adding the below settings under the system.webServer
.
<system.webServer>
<modules runAllManagedModulesForAllRequests="false">
<remove name="WebDAVModule" />
</modules>
</system.webServer>
Enter fullscreen mode
Exit fullscreen mode
There may be 2 web.config files in your project, if that is the case then update the one that is inside the wwwroot folder.
Also with the latest version of .Net CORE (2.0 and above), there might be a case of no web.config file available at all, if that is your case then add a web.config file on your own.
Note:
If you are facing the same issue with multiple APIs hosted on the same server, then either you can add the above entries under web.config
file of all the affected API’s or you can remove the below entry of WebDAVModule
from ApplicationHost.config
file under the module section:
<add name="WebDAVModule" />
Enter fullscreen mode
Exit fullscreen mode
ApplicationHost.config
can be found at
C:WindowsSystem32inetsrvconfig
Hope that helps !!!
Originally published at http://diary-techies.blogspot.com on
January 07, 2019.
If HttpPost/HttpDelete attributes won’t precisely reflect the Web API endpoint
To clarify, do you mean you have a situation where you specify HttpPost
but the user sends a GET
or DELETE
or other non-POST
method?
I believe this status message to be inappropriate, confusing and misleading.
I’m not sure I understand why you think this is an inappropriate response assuming I understand your scenario correctly. The server is making clear (via the attributes) which HTTP Method is allowed (POST
, DELETE
, etc.) and if the client uses a different one, we return a 405
error, to tell the client they used the wrong HTTP Method.
Can you provide a small runnable sample that reproduces the problem and describe what behavior you’d expect to see?
When Deployed to Live Environment
Ran into this issue after deploying our latest web app built on ASP.NET 5 (now known as Core 1.0), MVC 6 with Web API.
We are using HostForLife shared servers to host the site, a solid yet inexpensive choice for any of you looking for hosting recommendations.
We found that both GET and POST methods worked fine but neither PUT or DELETE worked at all. We were getting a 405 Method Not Allowed error.
From reading up on this it seems to be to do with something called WebDav disallowing these action names by default. You will need to override this setting in order to get it to work.
To do this open up your web.config file. There are 2 web.config files in your project – The one you want to open is the one INSIDE the wwwroot folder.
Inside the web.config add the following 3 lines inside the already existing system.webServer node:
<system.webServer> <modules runAllManagedModulesForAllRequests="false"> <remove name="WebDAVModule" /> </modules> </system.webServer>
Now just save your file and publish changes to your live site.
Update: Changes in ASP.NET Core 2.0 and Visual Studio 2017
It has recently come to my attention that in some of the newer project templates in Visual Studio 2017 (sidenote: if you haven’t moved from 2015 to 2017 yet then you should, it can be downloaded here) there is in fact no web.config file inside the wwwroot folder. One of the commenters below has mentioned that they just put the above code into the web.config file that was still there outside of the wwwroot folder at the project root level and that it worked fine there also.
I have also seen some newer project templates (as of April 2018) that did not include any web.config file at all. From what I have read there is a push to replace web.config files with appsettings.json files. In this instance I’m not actually sure what best practice is, as I have yet to deploy one of these newer ASP.NET Core websites. If anyone has any ideas let me know. My gut tells me that adding a web.config file yourself and adding in the code there should work fine, but as I said, there may be a newer, better way to do it in 2.0.