Rails API empty request headers - ruby-on-rails

Sending request from javascript to rails and providing the Authorization header with a token always shows up as empty headers on my rails API.
I have the follow piece of base code for all my API controllers:
module Api
class BaseController < ActionController::API
before_action :require_login
private
def require_login
unless signed_in?
head :unauthorized
end
end
def signed_in?
current_user.present?
end
def current_user
if request.headers['Authorization'].present?
User.find_by(token: request.headers['Authorization'])
else
nil
end
end
end
end
Doing my fetch request on javascript side like this:
fetch(`/api/clients?page=${page}`, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
'Authorization': AUTH_TOKEN
},
credentials: 'same-origin',
})
Fetching the value Authorization from request.headers always comes up as nil.
Anyone knows what might be going wrong?

Since you're using the Fetch() library, you can use the new Request() object which helps you customize your configurations.
// https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
var myHeaders = new Headers({
"Content-Type": "application/json",
'Authorization': AUTH_TOKEN
});
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default' };
var myRequest = new Request(`/api/clients?page=${page}, myInit);
fetch(myRequest).then(function(response) {
...
});
I had the same issue with Axios and it turns out I was using a get request with headers and params wrongly.
const params = { id: "ghjfsd7634" };
const headers = {
headers: {
"Content-Type": "application/json",
Authorization: token,
},
};
axios
.get(url, params, headers)
.then(function foo(response) {
handleResponse(response.data);
})
.catch(function foo(error) {
console.log("GET Resource Error");
console.log(error);
});
Correct way: Params and headers in get requests are passed differently compared to post, put etc. requests. Axios takes the entire config in the second argument, not a list of config objects. Put the params inside the config, and pass the entire object as the second argument:
const params = { id: "ghjfsd7634" };
const headers = {
"Content-Type": "application/json",
Authorization: token,
};
const config = { headers, params };
await axios
.get(url, config)
.then(function foo(response) {
handleResponse(response.data);
})
.catch(function foo(error) {
console.log("GET Resource Error");
console.log(error);
});

Related

handle post request with redux and rails api

i'm trying to post some data with fetch medthod in my api
export const CREATE_MATCH = 'CREATE_MATCH'
export function createMatch(user) {
const request = fetch("/api/matches", {
// Adding method type
method: "POST",
// Adding headers to the request
headers: {
"Content-type": "application/json",
"X-User-Token": user.authentication_token,
"X-User-Email": user.email
}
})
return {
type: CREATE_MATCH,
payload: request
}
}
but the i only get the response and not the data created
Response {type: "basic", url: "http://localhost:3000/api/matches", redirected: false, status: 200, ok: true, …}
i dont know how to get the data created.
in rails this is what i have, i dont have any data in a Match, only id and timestamps
def create
#match = Match.new
authorize #match
if #match.save
render json: #match
else
render_error
end
end
i've just find an answer with async / await function
export async function createMatch(user) {
const request = await fetch("/api/matches", {
// Adding method type
method: "POST",
// Adding body or contents to send
// body: JSON.stringify(),
// Adding headers to the request
headers: {
"Content-type": "application/json",
"X-User-Token": user.authentication_token,
"X-User-Email": user.email
}
})
const match = await request.json();
console.log(match)
return {
type: CREATE_MATCH,
payload: match
}
}

Missing parameters in Rails Controller when using multipart/form-data in React

I am using Rails and React with Axios to create a record. In my React app I collect all the data and put it inside of FormData like this:
const createVtc = () => {
let data = new FormData()
data.append('image', vtcImageToSend)
data.append('name', vtcName)
data.append('description', vtcDescription)
data.append('main_color', vtcColor)
data.append('minimum_age_to_join', vtcMinimumAge)
axios.post(`${ROOT_API}/v1/vtcs/create`, data, {
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'multipart/form-data'
}
}).then(res => {
console.log(res.data);
history.push('/dashboard')
}).catch(err => {
console.log(err);
})
};
This contains all the necessary data in order to create a record.
This is the Rails controller responsible for creating it:
def create
vtc = Vtc.new(vtc_params)
# other code is not important
end
And this is vtc_params private function:
def vtc_params
params.require(:vtc).permit(:id, :name, :description, :minimum_age_to_join, :main_color, :image)
end
Pretty standard stuff. It worked until I had to implement picture upload which made me switch to FormData upload and since then Rails throws this error:
ActionController::ParameterMissing in V1::VtcsController#create
param is missing or the value is empty: vtc
I can assume what's the problem but I don't know how to fix it. Before FormData I used to send it like this:
// other stuff
axios.post(`${ROOT_API}/v1/vtcs/create`, {
"vtc": {
"name": vtcName,
// etc.
}
}, {
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'multipart/form-data'
}
})
// other stuff
All of the data was inside of "vtc" object but now it's just data variable. I tried adding {"vtc": data} as Axios data which not surprisingly didn't work.
Just wrap your data variable in an object with the key vtc:
axios.post(`${ROOT_API}/v1/vtcs/create`, {vtc: data}, {
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'multipart/form-data'
}

Flutter HTTP Post returns 415

I have an issue with using Method.Post on my flutter app using http dart library. It seems that when I tried to post data from my WebAPI it gaves me a StatusCode 415. See my code below:
Code Login:
Future<User> login(User user) async {
print(URLRequest.URL_LOGIN);
return await _netUtil.post(Uri.encodeFull(URLRequest.URL_LOGIN), body: {
'username': user.username,
'password': user.password
}, headers: {
"Accept": "application/json",
}).then((dynamic res) {
print(res.toString());
});
}
Code NetworkUtils:
Future<dynamic> post(String url, {Map headers, body, encoding}) async {
return await http
.post(url, body: body, headers: headers, encoding: encoding)
.then((http.Response response) {
final String res = response.body;
final int statusCode = response.statusCode;
if (statusCode < 200 || statusCode > 400 || json == null) {
throw new Exception('Error while fetching data.');
}
return _decoder.convert(res);
});
}
Does anyone knew whats going on my code?
Try adding this new header:
headers: {
"Accept": "application/json",
"content-type":"application/json"
}
UPDATE
Ok now you need to send json data, like this :
import 'dart:convert';
var body = jsonEncode( {
'username': user.username,
'password': user.password
});
return await _netUtil.post(Uri.encodeFull(URLRequest.URL_LOGIN), body: body, headers: {
"Accept": "application/json",
"content-type": "application/json"
}).then((dynamic res) {
print(res.toString());
});
}
#Alvin Quezon
I met the same error as yours and fix it, please see below.
[Error]
StateError (Bad state: Cannot set the body fields of a Request with content-type "application/json".)
[Reason]
when you use the Flutter plug 'http.dart' method 'http.post()', you should read the document in detail below (note the black fonts):
Sends an HTTP POST request with the given headers and body to the given URL.
[body] sets the body of the request. It can be a [String], a [List<int>] or
a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
used as the body of the request. The content-type of the request will
default to "text/plain".
If [body] is a List, it's used as a list of bytes for the body of the
request.
If [body] is a Map, it's encoded as form fields using [encoding]. The content-type of the request will be set to "application/x-www-form-urlencoded"; this cannot be overridden.
[encoding] defaults to [utf8].
For more fine-grained control over the request, use [Request] or
[StreamedRequest] instead.
Future<Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.post(url, headers: headers, body: body, encoding: encoding));
[Solution]
So just encode your body as a string ,then you can set the header 'content-type' as 'application/json'.
see the codes of #diegoveloper answered!

With a `new Request` in Node/React, how to pass params with a GET request?

I have the following API call in my Reactjs app:
static getAllSkills(title_id) {
const request = new Request(`http://localhost:4300/api/v1/job_title_skills`, {
method: 'GET',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify({title_id: title_id})
});
return fetch(request).then(response => {
return response.json();
}).catch(error => {
return error;
});
}
Which points to a Rails endpoint which expects the param title_id like so:
def index
#skills = Skill.where(id: params[:title_id])
....
end
The controller is expecting a GET request however with the above, I'm getting the following JS console error:
Uncaught TypeError: Failed to construct 'Request': Request with GET/HEAD method cannot have body.
What is the right way to construct the Request and pass the param to the API?
I think the url in your api is waiting for the title_id maybe like:
api/v1/job_title_skills/:title_id
So you can append it in your url when you make the request:
static getAllSkills(title_id) {
const request = new Request(`http://localhost:4300/api/v1/job_title_skills/${title_id}`, {
headers: new Headers({
'Content-Type': 'application/json'
})
});
return fetch(request).then(response => {
return response.json();
}).catch(error => {
return error;
});
}

Rails Can't verify CSRF token authenticity ajax with X-CSRF-TOKEN header

I try to register a service worker endpoint in my database but when I send my post data with fetch the app raise an error.
I want to keep the csrf verification. Do you see something wrong ?
var ready;
ready = function(){
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported');
navigator.serviceWorker.register('/service-worker.js').then(function(reg) {
reg.pushManager.subscribe({
userVisibleOnly: true
}).then(function(sub) {
console.log('endpoint:', sub.endpoint);
console.log(sub);
var token = $('meta[name=csrf-token]').attr('content')
console.log(token);
return fetch('/register_endpoint', {
method: 'post',
headers: {
'Content-type': 'application/json',
'X-CSRF-TOKEN': token
},
body: JSON.stringify({
endpoint: sub.endpoint,
authenticity_token: token
})
});
});
}).catch(function(err) {
console.log('Erreur -> ', err);
});
}
};
$(document).ready(ready);
$(document).on('page:load',ready);
thanks
The problem is that the session cookie is not being sent if credentials is not specified as an option within fetch.
credentials has 3 possible values:
omit -> Doesn't send cookies
same-origin -> Only send cookies if the URL is on the same origin as the calling script
include -> Always send cookies, even for cross-origin calls
So this should probably work:
return fetch('/register_endpoint', {
method: 'post',
headers: {
'Content-type': 'application/json',
'X-CSRF-TOKEN': token
},
body: JSON.stringify({endpoint: sub.endpoint}),
credentials: 'same-origin'
})
Here more info about the native fetch api.
$ajax or fetch Both works in your case.
let token = function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))};
$.ajax({ url: URL,
type: 'POST',
beforeSend: token,
data: 'someData=' + someData,
success: function(response) {
$('#someDiv').html(response);
}
});
Or:
return fetch( URL, {
method: 'post',
headers: {
'Content-type': 'application/json',
'X-CSRF-TOKEN': token
},
body: JSON.stringify({endpoint: sub.endpoint}),
credentials: 'same-origin'
})
This is also not working for me so I have override this verified_request? method CsrfToken
class ApplicationController < ActionController::Base
protect_from_forgery
skip_before_action :verify_authenticity_token, if: :verified_request?
protected
def verified_request?
return true if request.get?
if respond_to?(:valid_authenticity_token?, true)
super || valid_authenticity_token?(session, URI.unescape(request.headers['X-CSRF-TOKEN'] || "") )
else
super || form_authenticity_token == URI.unescape(request.headers['X-CSRF-TOKEN'] || "")
end
end
end

Resources