C# Azure / Postman - 405 Method not Allowed error on POST - c#

I followed the following tutorial and set up my azure backend .
https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter2/custom/
I then installed postman for the first time and set :
http://apptest.azurewebsites.net/.auth/login/custom
{"username" : "adrian" , "password" : "supersecret"}
Json (application/json)
However , i keep getting this error :
405 Method not Allowed
{
"Message": "The requested resource does not support http method 'GET'."
}
Backend Code :
using System;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Claims;
using System.Web.Http;
using Newtonsoft.Json;
using AppTestService.Models;
using AppTestService.DataObjects;
using Microsoft.Azure.Mobile.Server.Login;
namespace AppTestService.Controllers
{
[Route(".auth/login/custom")]
public class CustomAuthController : ApiController
{
private AppTestContext db;
private string signingKey, audience, issuer;
public CustomAuthController()
{
db = new AppTestContext();
signingKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var website = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME");
audience = $"https://{website}/";
issuer = $"https://{website}/";
}
[System.Web.Http.HttpPost]
public IHttpActionResult Post([FromBody] User body)
{
if (body == null || body.Username == null || body.Password == null ||
body.Username.Length == 0 || body.Password.Length == 0)
{
return BadRequest();
}
if (!IsValidUser(body))
{
return Unauthorized();
}
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, body.Username)
};
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(
claims, signingKey, audience, issuer, TimeSpan.FromDays(30));
return Ok(new LoginResult()
{
AuthenticationToken = token.RawData,
User = new LoginResultUser { UserId = body.Username }
});
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool IsValidUser(User user)
{
return db.Users.Count(u => u.Username.Equals(user.Username) && u.Password.Equals(user.Password)) > 0;
}
}
public class LoginResult
{
[JsonProperty(PropertyName = "authenticationToken")]
public string AuthenticationToken { get; set; }
[JsonProperty(PropertyName = "user")]
public LoginResultUser User { get; set; }
}
public class LoginResultUser
{
[JsonProperty(PropertyName = "userId")]
public string UserId { get; set; }
}
}
If i add [System.Web.Http.HttpGet] on top of the function , then i get a different error. :
415 Unsupported media type
{
"Message": "The request contains an entity body but no Content-Type header. The inferred media type 'application/octet-stream' is not supported for this resource."
}
These are the headers :
Allow →POST
Content-Length →72
Content-Type →application/json; charset=utf-8
Date →Sat, 28 Jan 2017 22:08:48 GMT
Server →Microsoft-IIS/8.0
X-Powered-By →ASP.NET

You probably do want to make a POST request from PostMan instead of a GET. Don't add [HttpGet] on the action, just set the method to POST in PostMan.
And make sure you set the header Content-Type: application/json in PostMan.

Make sure in the headers of your request you are setting ContentType = "application/json" in postman or even when creating a request from any client.
Change your controller definition to
[RoutePrefix("auth/login/custom")]
public class CustomAuthController : ApiController
{
}
And just for test, introduce a route on POST like
[System.Web.Http.HttpPost]
[Route("post")]
public IHttpActionResult Post([FromBody] User body)
and try making a request to
http://apptest.azurewebsites.net/auth/login/custom/post

In Azure service api make sure it is with [HttpPost].
If you are able to send body param that means you have selected Post in postman while calling API which is correct. Dont change either of postman call or azure api to get. it will be mismatch.
If you have access to azure logs check if http Post is redirected to the https url as Get, in this case try calling https directly.
Azure logs looks as follows in this case:
Received request: POST http://xxx.azurewebsites.net/api/Data/test
Information Redirecting: https://xxx.azurewebsites.net/api/Data/test
Received request: GET https://xxx.azurewebsites.net/api/Data/test
in this case call https://xxx.azurewebsites.net/api/Data/test

Related

How to create ASP.NET Core Web API properly

I little unsure of my actions. I started my first web api application. So I wanna create api where is all functionality include authorization. And web and mobile applications which will work with my api. Something like that
But I discover problem on authorization step. I must handle many api's response variation like 401(unauthorized),200(OK) etc. I get response in Json format. And every response have own structure and its structure changes in differet cases. So that is the problem I can miss handle something and crash my app. How can I avoid it. May be I missunderstand something. I will be greateful for any help.
I create API on asp.net core framework.
Some response examples
OK 200
{"result":{"succeeded":true,"isLockedOut":false,"isNotAllowed":false,"requiresTwoFactor":false},"token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiI5YjkwZDFmZC1iMjQzLTRhMTEtYWQ3NS1iZWU0ZDJjNTJhNTEiLCJ1bmlxdWVfbmFtZSI6IkVlZm9zZmF0dXMxMzNAZ21haWwuY29tIiwibmJmIjoxNTkzOTU4MjM4LCJleHAiOjE1OTQwNDQ2MzgsImlhdCI6MTU5Mzk1ODIzOH0.AUjS7ocjp3Z_HuU1QqBPUG4NlNcRAihjOhbKBAC_6ecjjlZQM417M9KKGEk1uAr0yKFl9dcPNo04YPSKs-vJ7g"}
401 Unauthorized
{"type":"https://tools.ietf.org/html/rfc7235#section-3.1","title":"Unauthorized","status":401,"traceId":"|9ca7ae31-444c9220bfc1657b.1.88f5b6d2_"}
API's action for example
public async Task<IActionResult> Login(LoginModel loginModel)
{
if (ModelState.IsValid)
{
Microsoft.AspNetCore.Identity.SignInResult result = await _signInManager.PasswordSignInAsync
(loginModel.Email, loginModel.Password, loginModel.RememberMe, false);
if (result.Succeeded)
{
User user = await _userManager.FindByNameAsync(loginModel.Email);
ServiceResponse response = new ServiceResponse()
{
Result = result,
Token = CreateToken(user)
};
return Ok(response);
}
else
{
return Unauthorized();
}
}
return NoContent();
}
You should create a model for your responses and return all of your responses in that structure.
Example:
public class ResponseModel
{
public bool Succeeded { get; set; }
public string Message { get; set; }
public object Content { get; set; }
}
Ok Response structure :
{
"succeeded" : true,
"message" : "some-message",
"content" : {
"isLockedOut" : false,
"isNotAllowed" : false,
...
..
}
}
UnAuthorized Response structure :
{
"succeeded" : false,
"message" : "some-message",
"content" : {
"title" : "unauthorized",
"status" : "401",
...
...
}
}

Azure Functions OAuth2 from email / password stored in database?

I have a database that contains emails and password hashes.
I would like to secure http trigger's from Azure Functions to allow only authorized call thanks to the Authorization header with a BEARER token.
I think I will need
an http trigger that will generate the token from email/password
Authorize and authenticate the user based on the Authorization header
Can someone get me started on how to create a custom authentication provider or use an existing one and configure Azure Functions to work with it?
Microsoft identity platform supports the OAuth 2.0 Resource Owner Password Credentials (ROPC) grant, which allows an application to sign in the user by directly handling their password.
Get the email(username) and password from database, and send the following request to receive the access token.
POST {tenant}/oauth2/v2.0/token
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&scope=user.read%20openid%20profile%20offline_access
&username=MyUsername#myTenant.com
&password=SuperS3cret
&grant_type=password
You could have look following code snippet, I have tested on azure portal , Azure Function V2:
#r "Newtonsoft.Json"
using Newtonsoft.Json;
using System.Net;
using System.Net.Http.Headers;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
try
{
//Parse query parameter
log.LogInformation("C# HTTP trigger function processed a request.");
//Read Request Body
var content = await new StreamReader(req.Body).ReadToEndAsync();
//Extract Request Body and Parse To Class
UserAuthentication objUserInfo = JsonConvert.DeserializeObject<UserAuthentication>(content);
//Message Container
dynamic validationMessage;
//Validate required param
if (string.IsNullOrEmpty(objUserInfo.UserName.Trim()))
{
validationMessage = new OkObjectResult("User name is required!");
return (IActionResult)validationMessage;
}
if (string.IsNullOrEmpty(objUserInfo.Password.Trim()))
{
validationMessage = new OkObjectResult("Password is required!");
return (IActionResult)validationMessage;
}
// Authentication Token Request format
string tokenUrl = $"https://login.microsoftonline.com/common/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["client_id"] = "YourApplicationId",
["client_secret"] = "YourApplicationPassword",
["resource"] = "https://graph.microsoft.com",
["username"] = "" + objUserInfo.UserName + "",
["password"] = "" + objUserInfo.Password + ""
});
// Request For Token Endpoint
using (var _client = new HttpClient())
{
var tokenResponse = await _client.SendAsync(tokenRequest);
AccessTokenClass objAccessToken = JsonConvert.DeserializeObject<AccessTokenClass>(await tokenResponse.Content.ReadAsStringAsync());
// When Token Request Null
if (objAccessToken.access_token == null)
{
validationMessage = new OkObjectResult("Invalid Authentication! Please Check Your Credentials And Try Again!");
return (IActionResult)validationMessage;
}
else
{
return new OkObjectResult(objAccessToken.access_token);
}
}
}
catch (Exception ex)
{
validationMessage = new OkObjectResult("Sorry something went wrong! Please check your given information and try again! {0}" + ex.Message);
return (IActionResult)validationMessage;
}
}
Class I have Used:
UserAuthentication Class
public class UserAuthentication
{
public string UserName { get; set; }
public string Password { get; set; }
}
public class AzureFunctionCreateUserClass
{
public string access_token { get; set; }
public string expires_in { get; set; }
public string token_type { get; set; }
public string resource { get; set; }
}
Note: This an sample for azure portal which I have written on azure function . So try to run on there.
Hope this would help.

.NET MVC Check header before allowing response

I have created a Login system that provides the user with an API-key on successful login. The API key has an access level attached to it.
I am sending the API key in the header with every Ajax request from my front end, however it is no longer sending a correct response.
This is one of my controllers:
[AllowCrossSiteJson]
public class WebOrdersController : Controller
{
protected IWebOrdersService _webOrdersService = new WebOrdersService();
// GET: WebOrders
public ActionResult Index()
{
return View();
}
[WebOrdersAPIFilter]
public String GetTotals()
{
return _webOrdersService.GetAllTotals();
}
}
And this is my Filter class:
public class WebOrdersAPIFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
IAccountsService _accountsService = new AccountsService();
filterContext.RequestContext.HttpContext.Response.AddHeader("Authority", "*");
var rawUrl = HttpContext.Current.Request.RawUrl;
var routeArray = rawUrl.Split('/');
var route = routeArray[0].ToLower();
if (String.IsNullOrEmpty(route))
{
route = routeArray[1].ToLower();
}
var headers = filterContext.HttpContext.Request.Headers;
// Ensure that all of your properties are present in the current Request
if (!String.IsNullOrEmpty(headers["Authority"]))
{
var apiKey = headers["Authority"];
//GET API LEVEL
int accessLevel = _accountsService.CheckAPIKey(apiKey);
if(route == "weborders" && (accessLevel != 1 || accessLevel != 3 || accessLevel != 6))
{
filterContext.Result = new RedirectResult("http://localhost/error/unauthorised");
}
}
else
{
filterContext.Result = new RedirectResult("http://localhost/error/notloggedin");
}
base.OnActionExecuting(filterContext);
}
}
If the user is not logged in, the API Key is not sent in the header, so if it does not exist it is safe to say they are logged out. If they are logged in, The key is sent. I am then getting the access level of they key and comparing it to the route.
I have debugged it and found that the route and accessLevel variables are working correctly however even then it does not return and response and if the IF() statement fails, it is returning the html for the page /error/unauthorised or /error/notloggedin and not actually redirecting.
How can I achieve the functionality i require, to ensure that the user requesting the data is authorised?

Enabling Windows and Basic Authentication for ASP.NET Web API 2 Application

I have an ASP.NET Web API 2 application that uses Windows Authentication for all the controllers. I have a need now for some controllers to use Basic Authentication.
I know it is not possible to enable both Anonymous and Windows Authentications, but is it possible to enable Windows Authentication for some controllers and Basic Authentication for some others?
UPDATE:
Implemented a filter as shown on the article EdSF shared
Here is what I have so far:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class BasicAuthenticationFilter : AuthorizationFilterAttribute
{
private bool _active = true;
private const string ValidUsername = #"Test";
private const string ValidPassword = #"T3st";
public BasicAuthenticationFilter()
{
}
public BasicAuthenticationFilter(bool active)
{
_active = active;
}
public override void OnAuthorization(HttpActionContext actionContext)
{
if (_active)
{
var identity = ParseAuthorizationHeader(actionContext);
if (identity == null)
{
Challenge(actionContext);
return;
}
if (!OnAuthorizeUser(identity.Name, identity.Password, actionContext))
{
Challenge(actionContext);
return;
}
var principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
base.OnAuthorization(actionContext);
}
}
protected virtual bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) ||
!username.Equals(ValidUsername) || !password.Equals(ValidPassword))
{
return false;
}
return true;
}
protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpActionContext actionContext)
{
string authHeader = null;
var auth = actionContext.Request.Headers.Authorization;
if (auth != null && auth.Scheme == "Basic")
{
authHeader = auth.Parameter;
}
if (string.IsNullOrEmpty(authHeader)) return null;
authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));
var tokens = authHeader.Split(':');
if (tokens.Length < 2) return null;
return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
}
private void Challenge(HttpActionContext actionContext)
{
var host = actionContext.Request.RequestUri.DnsSafeHost;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", host));
}
}
BasicAuthenticationIdentity Class:
public class BasicAuthenticationIdentity : GenericIdentity
{
public BasicAuthenticationIdentity(string name, string password)
: base(name, "Basic")
{
Password = password;
}
public string Password { get; set; }
}
Also, decorated my controller with the Basic Authentication Filter:
[BasicAuthenticationFilter]
[RoutePrefix("api/BasicAuth")]
public class BasicAuthController : ApiController
{
//[BasicAuthenticationFilter]
[HttpGet]
public IHttpActionResult TestBasicAuth()
{
return Ok("success");
}
}
When I make a call to api/BasicAuth from Fiddler I got back 401, but it only returns the following challenges:
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
At this point Fiddler tries it again but this time instead of passing Basic Authorization Scheme, it passes Negotiate. This one fails also.
Then, when Fiddler finally tries the third time, my filter actually gets the request, but since authorization scheme is Negotiate instead of Basic, my filter returns Unauthorized also.
Is there a way to force the controller to just use the BasicAuthenticationFilter?
Thanks in advance
You can - because BASIC AUTH credentials are sent in HTTP Headers (base64 encoded only). You don't have to "enable" anything at the application level and handle HTTP requests to your API endpoints "manually" (by inspecting headers).
e.g. Basic Auth Header: Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
See this example that creates an AuthorizationFilter that can be applied to Controller or Action, or even globally if needed...
Hth.

WebApi - The requested resource does not support http method 'GET'

Question Background:
I have a basic WebApi project hosted as a WebApp in Azure.
The Issue:
The problem I have is If I access any method other than a 'GET' type then I'm receiving the following error in my JSON response:
The requested resource does not support http method 'GET'
The Code:
The following code is how the project currently is.
RouteConfig.cs class:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Home",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
The ValuesController controller class:
public class ValuesController : ApiController
{
private List<CompanyData> _company;
public ValuesController()
{
_company = new List<CompanyData>
{
new CompanyData
{
CompanyName = "SmallTech.Ltd",
CompanyOwner = "John Smith",
CompanyIndustry = "Electronic Components",
EmployeeNo = "3"
}
};
}
public List<CompanyData> GetCompanyData()
{
return _company;
}
//GET api/values
public IEnumerable<string> Get()
{
return new string[] { "Test GET Method"};
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post(string value)
{
string test = value;
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete]
public void Delete(int id)
{
}
An example of calling the above Delete method when the error occurs is:
http://testwebapisite.azurewebsites.net/api/values/Delete/5
I have read other people having the same issue and using the HTTP attributes from the System.Net.MVC. I can confirm I'm not using this and am using `System.Net.Http.HttpPostAttribute.
Any help working out why I'm receiving the GET error message would be great.
You are trying to access an action which clearly specifies delete as its verb via a GET request.
By default the browser will do a GET request if you paste a url so thats pretty much easy to test but for the other verbs you'll have to use an actual rest/http client to specify the verb. You can use Postman or Rest Console if you use chrome to dev/test
In addition to those tools, you might want to have fiddler installed .. it will help you track all http activity (both sent/received) you'll know exactly what you are sending and receiving from the wire
You could also do this from code if you want using HttpClient.
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://testwebapisite.azurewebsites.net/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.DeleteAsync("api/values/5");
}
You haven't shown the code that you are using to invoke the API, but I suspect you are not using the DELETE HTTP verb. The resource you are accessing has URI or http://testwebapisite.azurewebsites.net/api/values/5 - note the action name is not specified. Rather, as the comment of your method suggests, you should be using the DELETE HTTP verb. Example:
using (var client = new HttpClient())
await client.DeleteAsync("http://testwebapisite.azurewebsites.net/api/values/5");

Categories

Resources