WebApi 2 + Owin Use urlencoded body on token request - oauth

In my WebAPI2 app I use OAuth authentication through Owin middleware. In order to get token client should use application/x-www-form-urlencoded body in request.
function userAccount($resource, appSettings) {
return {
registration: $resource(appSettings.serverPath + "/api/Account/Register", null,
{
'registerUser' : { method : 'POST'}
}
),
login : $resource(appSettings.serverPath + "/Token", null,
{
'loginUser': {
method: 'POST',
headers: {
'Content-Type' : 'application/x-www-form-urlencoded'
},
transformRequest: function (data, headersGetter) {
var str = [];
for (var d in data) {
str.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
}
return str.join("&");
}
}
}
)
}
}
But is there any method to override this behaviour to use raw body in json format? Instead of this: "grant_type=password&username=user&password=123456" want to use this: "{ grant_type: "password", username:"user", password="123456" }".
Appreciate any suggests.

You could set up an action in a controller as a "proxy" method that could accept the json in the body and then call the internal method with the url encoded parameters.

Related

Add a Bearer Token to a Breezejs fetchMetadata Call

My breeze services works great. But I just moved it behind a WSO2 API Manager. It now needs a Bearer Token for each call.
I have the Bearer Token. But I can't seem to figure out how to add it to the Metadata Call.
I tried something like this. But it did not add a header to the metadata call:
var ajaxAdapter: any = breeze.config.getAdapterInstance('ajax');
ajaxAdapter.defaultSettings = {
headers: {
"X-Test-Header": "foo2"
}
}
Does the fetchMetadata use a different system from the ajax adapter?
How can I add a header to the Fetch Metadata call?
Turns out I was using the Fetch API. So I had to do it that way. Here is what my setup looks like:
setupFetchClient() {
let httpClient = new HttpClient();
httpClient.configure(config => {
config.withDefaults({
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
})
.withInterceptor({
request(request: Request) {
let accessToken = getAccessToken();
request.headers.append('Authorization', 'Bearer ' + accessToken);
return request;
},
responseError(error) {
return error;
}
})
.useStandardConfiguration();
});
// Aurelia Specific Code.
Container.instance.registerInstance(HttpClient, httpClient);
}

How to pass 3 argument to backend with `POST` request?

According to backend, I require to pass 3 argument through post request, this backend function is:
public ResponseModel Post([FromBody] CourseFileUpload item, string fileName, Stream fileToUpload)
now I am trying to pass the argument like this:
uploadFile(uploadData:ModelToFileSteam):Observable<ModelToFileSteam> {
const fileName = uploadData.fileName;
console.log('file name is', fileName);
const headers = new HttpHeaders({ 'Content-Type': 'application/json', 'Access-Control-Allow-Origin':'*' });
return this.http.post<ModelToFileSteam>(environment.baseUrl+`CourseFileUpload`, uploadData.fileToUpload, uploadData.fileName, uploadData.uploadStream)
.pipe(
map(data => {
return data;
} ),
catchError(this.handleError)
)
}
But getting error, not able to pass 3 arguments at all. what is the correct way to do this?
any one help me?
I will suggest wrapping all in a single object. And send it to backend.
Or just send uploadData
return this.http.post<ModelToFileSteam>(environment.baseUrl+`CourseFileUpload`, uploadData)
.pipe(
map(data => {
return data;
} ),
catchError(this.handleError)
)
And in the backend, you can get uploadDate like req.body.uploadData
To check you can console.log(uploadData.fileName);
its my working example
this.http.post<Customer>(this.base_url + 'v1/customers', client, this.getHeaders());
Where client is customer object and this.getHeaders() is:
getHeaders() {
return {
headers: new HttpHeaders({
'Content-Type': 'application/json; charset=utf-8',
})
};
}
Good luck!

simple rest data provider won't accept Odata data

So the API I am using is using ODATA (https://www.odata.org/). As a result this is what it looks like when it's returned
```
{"#odata.context":"http://localhost:5001/api/$metadata#apemp",
"value":[
{"EmpID":1,
"Abbr":"Admin",
"BadgeNo":null,
"ColorRef":0,
"ContactMethodID":null,
"DateHired":"2018-05-25T16:42:57-05:00"}
]```
our data provider looks like this
import { stringify } from 'query-string';
import {
fetchUtils,
GET_LIST,
GET_ONE,
GET_MANY,
GET_MANY_REFERENCE,
CREATE,
UPDATE,
UPDATE_MANY,
DELETE,
DELETE_MANY
} from 'react-admin';
...
const convertHTTPResponse = (response, type, resource, params) => {
const { headers, json } = response;
switch (type) {
case GET_LIST:
return { data: json };
case GET_MANY_REFERENCE:
if (!headers.has('content-range')) {
throw new Error(
'The Content-Range header is missing in the HTTP Response. The simple REST data provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?'
);
}
return {
data: json,
total: parseInt(
headers
.get('content-range')
.split('/')
.pop(),
10
)
};
case CREATE:
return { data: { ...params.data, id: json.id } };
default:
return { data: json };
}
};
return (type, resource, params) => {
// simple-rest doesn't handle filters on UPDATE route, so we fallback to calling UPDATE n times instead
if (type === UPDATE_MANY) {
return Promise.all(
params.ids.map(id =>
httpClient(`${apiUrl}/${resource}/${id}`, {
method: 'PUT',
body: JSON.stringify(params.data)
})
)
).then(responses => ({
data: responses.map(response => response.json)
}));
}
// simple-rest doesn't handle filters on DELETE route, so we fallback to calling DELETE n times instead
if (type === DELETE_MANY) {
return Promise.all(
params.ids.map(id =>
httpClient(`${apiUrl}/${resource}/${id}`, {
method: 'DELETE'
})
)
).then(responses => ({
data: responses.map(response => response.json)
}));
}
const { url, options } = convertDataRequestToHTTP(type, resource, params);
return httpClient(url, options).then(response =>
convertHTTPResponse(response, type, resource, params)
);
};
};
so right now when I point that data provider to api endpoints it doesn't look the way the code is formatted.
I get the error
"The response to 'GET_LIST' must be like { data : [...] }, but the received data is not an array. The dataProvider is probably wrong for 'GET_LIST'"
I have used this dataprovider extensively with our previous API which returned data slightly differently.
since odata returns an object with the context and url as the items and the value with an array as the second item it doesn't work.
I really just need the array but don't know what I should be writing to get that.
I am also implementing DataProvider for OData and what you need to do is that you have to "wrap" your response data stored in value with data object.
But the array is in value prop and you can't just assert return { data: json }; like you do, because it expects object like {data: [0: {prop1: "hello", prop2: "world"}, 1: { ... }]} and you return this:
{
"#odata.context":"http://localhost:5001/api/$metadata#apemp",
"value":[
{
"EmpID":1,
"Abbr":"Admin",
"BadgeNo":null,
"ColorRef":0,
"ContactMethodID":null,
"DateHired":"2018-05-25T16:42:57-05:00"
}
]
}
So what you need to do is to assign value prop of response json, eg:
return { data: json.value };
So its actually pretty easy to fix

Angular2 Http post request not binding to ASP.NET 5 controller's action

I am initiating a post request from Angular2 to a ASP.NET 5 controller action. Angular is posting the data correctly and hitting the controller action but it is not being mapped to the parameters defined in controller action, the parameters are null. Meanwhile by inspecting through Request object Request.Form has the correct textual data but not binding to the model.
Angular
let body = JSON.stringify({ firstName: 'Ali' });
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
this.http.post(this.url, body, { headers: headers })
.subscribe(
(data) => {
console.log('Response received');
console.log(data);
},
(err) => { console.log('Error'); },
() => console.log('Authentication Complete')
);
ASP.NET
[HttpPost]
public IActionResult DemoAction(string firstName)
{
var req = Request;
return null;
}
Request.Form has data in the form like {\"firstName\":\"Ali\"} but the parameter firstName is null
You try to send a JSON content (created using the JSON.stringify method) with a content type url encoded form.
You should try to use the application/json one:
let body = JSON.stringify({ firstName: 'Ali' });
let headers = new Headers({ 'Content-Type': 'application/json' });
this.http.post(this.url, body, { headers: headers })
Edit
If you want to provide a form content, you could leverage the URLSearchParams class:
var params = new URLSearchParams();
params.set('firstName', 'Ali');
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
this.http.post(this.url, params.toString(), { headers: headers })
In this case you don't require to pass the header. look at here. you may try this.
this.http.post('/api/ControllerName/DemoAction?firstName=Ali')
.subscribe(
(data) => {
console.log('Response received');
console.log(data);
},
(err) => { console.log('Error'); },
() => console.log('Authentication Complete')
);
This will surely work.
Edited:
Figured out your problem.
First: When you are receiving paramter(s) at API end , you must use above portion.
second: When you are receiving object at API end, you must use below code.
I'm showing you with my setup as I don't know your object at server side.
let um=JSON.stringify({ Username: "Hello1",Password:"Hello2"});
let headers = new Headers({ 'Content-Type': 'application/json'});
this.http.post(this.url+'/Authentication',um,{ headers: headers })
.subscribe(...);
At server side I have following setup.
public class UserModel
{
public string Username { get; set; }
public string Password { get; set; }
}
[HttpPost]
public IHttpActionResult Authentication(UserModel um) // I m getting value here.
{
if (um.Username == "a" && um.Password == "a")
{
um.Username = um.Username;
um.Password = um.Password;
return Ok(um);
}
return NotFound();
}
Simple solution !! isn't it?
For me it works:
let headers = new Headers({ 'Content-Type': 'application/json' });
this.http.post(this.url, JSON.stringify({ firstName: 'Ali' }), { headers: headers })

How can I supply an AntiForgeryToken when posting JSON data using $.ajax?

I am using the code as below of this post:
First I will fill an array variable with the correct values for the controller action.
Using the code below I think it should be very straightforward by just adding the following line to the JavaScript code:
data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val();
The <%= Html.AntiForgeryToken() %> is at its right place, and the action has a [ValidateAntiForgeryToken]
But my controller action keeps saying: "Invalid forgery token"
What am I doing wrong here?
Code
data["fiscalyear"] = fiscalyear;
data["subgeography"] = $(list).parent().find('input[name=subGeography]').val();
data["territories"] = new Array();
$(items).each(function() {
data["territories"].push($(this).find('input[name=territory]').val());
});
if (url != null) {
$.ajax(
{
dataType: 'JSON',
contentType: 'application/json; charset=utf-8',
url: url,
type: 'POST',
context: document.body,
data: JSON.stringify(data),
success: function() { refresh(); }
});
}
You don't need the ValidationHttpRequestWrapper solution since MVC 4. According to this link.
Put the token in the headers.
Create a filter.
Put the attribute on your method.
Here is my solution:
var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers['__RequestVerificationToken'] = token;
$.ajax({
type: 'POST',
url: '/MyTestMethod',
contentType: 'application/json; charset=utf-8',
headers: headers,
data: JSON.stringify({
Test: 'test'
}),
dataType: "json",
success: function () {},
error: function (xhr) {}
});
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var httpContext = filterContext.HttpContext;
var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
AntiForgery.Validate(cookie != null ? cookie.Value : null, httpContext.Request.Headers["__RequestVerificationToken"]);
}
}
[HttpPost]
[AllowAnonymous]
[ValidateJsonAntiForgeryToken]
public async Task<JsonResult> MyTestMethod(string Test)
{
return Json(true);
}
What is wrong is that the controller action that is supposed to handle this request and which is marked with the [ValidateAntiForgeryToken] expects a parameter called __RequestVerificationToken to be POSTed along with the request.
There's no such parameter POSTed as you are using JSON.stringify(data) which converts your form to its JSON representation and so the exception is thrown.
So I can see two possible solutions here:
Number 1: Use x-www-form-urlencoded instead of JSON for sending your request parameters:
data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val();
data["fiscalyear"] = fiscalyear;
// ... other data if necessary
$.ajax({
url: url,
type: 'POST',
context: document.body,
data: data,
success: function() { refresh(); }
});
Number 2: Separate the request into two parameters:
data["fiscalyear"] = fiscalyear;
// ... other data if necessary
var token = $('[name=__RequestVerificationToken]').val();
$.ajax({
url: url,
type: 'POST',
context: document.body,
data: { __RequestVerificationToken: token, jsonRequest: JSON.stringify(data) },
success: function() { refresh(); }
});
So in all cases you need to POST the __RequestVerificationToken value.
I was just implementing this actual problem in my current project. I did it for all Ajax POSTs that needed an authenticated user.
First off, I decided to hook my jQuery Ajax calls so I do not to repeat myself too often. This JavaScript snippet ensures all ajax (post) calls will add my request validation token to the request. Note: the name __RequestVerificationToken is used by the .NET framework so I can use the standard Anti-CSRF features as shown below.
$(document).ready(function () {
securityToken = $('[name=__RequestVerificationToken]').val();
$('body').bind('ajaxSend', function (elm, xhr, s) {
if (s.type == 'POST' && typeof securityToken != 'undefined') {
if (s.data.length > 0) {
s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
}
else {
s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
}
}
});
});
In your Views where you need the token to be available to the above JavaScript code, just use the common HTML-Helper. You can basically add this code wherever you want. I placed it within a if(Request.IsAuthenticated) statement:
#Html.AntiForgeryToken() // You can provide a string as salt when needed which needs to match the one on the controller
In your controller simply use the standard ASP.NET MVC anti-CSRF mechanism. I did it like this (though I actually used a salt).
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
// Do something
return Json(true);
}
With Firebug or a similar tool you can easily see how your POST requests now have a __RequestVerificationToken parameter appended.
You can set $.ajax 's traditional attribute and set it to true, to send json data as url encoded form. Make sure to set type:'POST'. With this method you can even send arrays and you do not have to use JSON.stringyfy or any changes on server side (e.g. creating custom attributes to sniff header )
I have tried this on ASP.NET MVC3 and jquery 1.7 setup and it's working
following is the code snippet.
var data = { items: [1, 2, 3], someflag: true};
data.__RequestVerificationToken = $(':input[name="__RequestVerificationToken"]').val();
$.ajax({
url: 'Test/FakeAction'
type: 'POST',
data: data
dataType: 'json',
traditional: true,
success: function (data, status, jqxhr) {
// some code after succes
},
error: function () {
// alert the error
}
});
This will match with MVC action with following signature
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult FakeAction(int[] items, bool someflag)
{
}
You won't ever have to validate an AntiForgeryToken when you receive posted JSON.
The reason is that AntiForgeryToken has been created to prevent CSRF. Since you can't post AJAX data to another host and HTML forms can't submit JSON as the request body, you don't have to protect your app against posted JSON.
I have resolved it globally with RequestHeader.
$.ajaxPrefilter(function (options, originalOptions, jqXhr) {
if (options.type.toUpperCase() === "POST") {
// We need to add the verificationToken to all POSTs
if (requestVerificationTokenVariable.length > 0)
jqXhr.setRequestHeader("__RequestVerificationToken", requestVerificationTokenVariable);
}
});
where the requestVerificationTokenVariable is an variable string that contains the token value.
Then all ajax call send the token to the server, but the default ValidateAntiForgeryTokenAttribute get the Request.Form value.
I have writed and added this globalFilter that copy token from header to request.form, than i can use the default ValidateAntiForgeryTokenAttribute:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new GlobalAntiForgeryTokenAttribute(false));
}
public class GlobalAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
private readonly bool autoValidateAllPost;
public GlobalAntiForgeryTokenAttribute(bool autoValidateAllPost)
{
this.autoValidateAllPost = autoValidateAllPost;
}
private const string RequestVerificationTokenKey = "__RequestVerificationToken";
public void OnAuthorization(AuthorizationContext filterContext)
{
var req = filterContext.HttpContext.Request;
if (req.HttpMethod.ToUpperInvariant() == "POST")
{
//gestione per ValidateAntiForgeryToken che gestisce solo il recupero da Request.Form (non disponibile per le chiamate ajax json)
if (req.Form[RequestVerificationTokenKey] == null && req.IsAjaxRequest())
{
var token = req.Headers[RequestVerificationTokenKey];
if (!string.IsNullOrEmpty(token))
{
req.Form.SetReadOnly(false);
req.Form[RequestVerificationTokenKey] = token;
req.Form.SetReadOnly(true);
}
}
if (autoValidateAllPost)
AntiForgery.Validate();
}
}
}
public static class NameValueCollectionExtensions
{
private static readonly PropertyInfo NameObjectCollectionBaseIsReadOnly = typeof(NameObjectCollectionBase).GetProperty("IsReadOnly", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);
public static void SetReadOnly(this NameValueCollection source, bool readOnly)
{
NameObjectCollectionBaseIsReadOnly.SetValue(source, readOnly);
}
}
This work for me :)
You can't validate an content of type contentType: 'application/json; charset=utf-8' because your date will be uploaded not in the Form property of the request, but in the InputStream property, and you will never have this Request.Form["__RequestVerificationToken"].
This will be always empty and validation will fail.
I hold the token in my JSON object and I ended up modifying the ValidateAntiForgeryToken class to check the InputStream of the Request object when the post is json. I've written a blog post about it, hopefully you might find it useful.
Check out Dixin's Blog for a great post on doing exactly that.
Also, why not use $.post instead of $.ajax?
Along with the jQuery plugin on that page, you can then do something as simple as:
data = $.appendAntiForgeryToken(data,null);
$.post(url, data, function() { refresh(); }, "json");
AJAX based model posting with AntiForgerytoken can be made bit easier with Newtonsoft.JSON library
Below approach worked for me:
Keep your AJAX post like this:
$.ajax({
dataType: 'JSON',
url: url,
type: 'POST',
context: document.body,
data: {
'__RequestVerificationToken': token,
'model_json': JSON.stringify(data)
};,
success: function() {
refresh();
}
});
Then in your MVC action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FormCollection data) {
var model = JsonConvert.DeserializeObject < Order > (data["model_json"]);
return Json(1);
}
Hope this helps :)
I had to be a little shady to validate anti-forgery tokens when posting JSON, but it worked.
//If it's not a GET, and the data they're sending is a string (since we already had a separate solution in place for form-encoded data), then add the verification token to the URL, if it's not already there.
$.ajaxSetup({
beforeSend: function (xhr, options) {
if (options.type && options.type.toLowerCase() !== 'get' && typeof (options.data) === 'string' && options.url.indexOf("?__RequestVerificationToken=") < 0 && options.url.indexOf("&__RequestVerificationToken=") < 0) {
if (options.url.indexOf('?') < 0) {
options.url += '?';
}
else {
options.url += '&';
}
options.url += "__RequestVerificationToken=" + encodeURIComponent($('input[name=__RequestVerificationToken]').val());
}
}
});
But, as a few people already mentioned, the validation only checks the form - not JSON, and not the query string. So, we overrode the attribute's behavior. Re-implementing all of the validation would have been terrible (and probably not secure), so I just overrode the Form property to, if the token were passed in the QueryString, have the built-in validation THINK it was in the Form.
That's a little tricky because the form is read-only, but doable.
if (IsAuth(HttpContext.Current) && !IsGet(HttpContext.Current))
{
//if the token is in the params but not the form, we sneak in our own HttpContext/HttpRequest
if (HttpContext.Current.Request.Params != null && HttpContext.Current.Request.Form != null
&& HttpContext.Current.Request.Params["__RequestVerificationToken"] != null && HttpContext.Current.Request.Form["__RequestVerificationToken"] == null)
{
AntiForgery.Validate(new ValidationHttpContextWrapper(HttpContext.Current), null);
}
else
{
AntiForgery.Validate(new HttpContextWrapper(HttpContext.Current), null);
}
}
//don't validate un-authenticated requests; anyone could do it, anyway
private static bool IsAuth(HttpContext context)
{
return context.User != null && context.User.Identity != null && !string.IsNullOrEmpty(context.User.Identity.Name);
}
//only validate posts because that's what CSRF is for
private static bool IsGet(HttpContext context)
{
return context.Request.HttpMethod.ToUpper() == "GET";
}
...
internal class ValidationHttpContextWrapper : HttpContextBase
{
private HttpContext _context;
private ValidationHttpRequestWrapper _request;
public ValidationHttpContextWrapper(HttpContext context)
: base()
{
_context = context;
_request = new ValidationHttpRequestWrapper(context.Request);
}
public override HttpRequestBase Request { get { return _request; } }
public override IPrincipal User
{
get { return _context.User; }
set { _context.User = value; }
}
}
internal class ValidationHttpRequestWrapper : HttpRequestBase
{
private HttpRequest _request;
private System.Collections.Specialized.NameValueCollection _form;
public ValidationHttpRequestWrapper(HttpRequest request)
: base()
{
_request = request;
_form = new System.Collections.Specialized.NameValueCollection(request.Form);
_form.Add("__RequestVerificationToken", request.Params["__RequestVerificationToken"]);
}
public override System.Collections.Specialized.NameValueCollection Form { get { return _form; } }
public override string ApplicationPath { get { return _request.ApplicationPath; } }
public override HttpCookieCollection Cookies { get { return _request.Cookies; } }
}
There's some other stuff that's different about our solution (specifically, we're using an HttpModule so we don't have to add the attribute to every single POST) that I left out in favor of brevity. I can add it if necessary.
Unfortunately for me, the other answers rely on some request formatting handled by jquery, and none of them worked when setting the payload directly. (To be fair, putting it in the header would have worked, but I did not want to go that route.)
To accomplish this in the beforeSend function, the following works. $.params() transforms the object into the standard form / url-encoded format.
I had tried all sorts of variations of stringifying json with the token and none of them worked.
$.ajax({
...other params...,
beforeSend: function(jqXHR, settings){
var token = ''; //get token
data = {
'__RequestVerificationToken' : token,
'otherData': 'value'
};
settings.data = $.param(data);
}
});
```
You should place AntiForgeryToken in a form tag:
#using (Html.BeginForm(actionName:"", controllerName:"",routeValues:null, method: FormMethod.Get, htmlAttributes: new { #class="form-validator" }))
{
#Html.AntiForgeryToken();
}
Then in javascript modify the following code to be
var DataToSend = [];
DataToSend.push(JSON.stringify(data), $('form.form-validator').serialize());
$.ajax({
dataType: 'JSON',
contentType: 'application/json; charset=utf-8',
url: url,
type: 'POST',
context: document.body,
data: DataToSend,
success: function() {
refresh();
}
});
Then you should be able to validate the request using ActionResult annotations
[ValidateAntiForgeryToken]
[HttpPost]
I hope this helps.

Resources