I have a simple Ember 2.4 application. Here's the relevant code
../models/invitation.js
import DS from 'ember-data';
export default DS.Model.extend({
email: DS.attr('string')
});
../controllers/index.js
import Ember from 'ember';
export default Ember.Controller.extend({
emailAddress: '',
actions: {
saveInvitation() {
const email = this.get('emailAddress');
const newInvitation = this.store.createRecord('invitation', {
email: email,
});
newInvitation.save();
}
}
});
When I trigger the saveInvitation action, my Rails backend does not receive any parameters. The right action/controller receives the request but the only parameters are {action: "create", controller: "invitations"}.
Am I missing something? Shouldn't the parameters include the invitation with the email?
It seems that the default Ember adapter sends a content type application/vdn.api+json which is not automatically registered by Rails and parse the JSON arguments.
A fix for this was to register the right mime types under rails config
api_mime_types = %W(
application/vnd.api+json
text/x-json
application/json
)
Mime::Type.unregister :json
Mime::Type.register 'application/json', :json, api_mime_types
See https://github.com/rails-api/active_model_serializers/issues/1027
add following to config/initializers/json_adapter.rb
ActiveSupport.on_load(:action_controller) do
require 'active_model_serializers/register_jsonapi_renderer'
end
This may helps
Related
I am using Rails as a backend API to support a frontend that is made with VueJS. I want to send a POST request from VueJS when a button is pressed on the website, I have used axios to do this
`
methods: {
// Performs a POST request to the rails endpoint and passes the email and pass as parameters
signup() {
if (this.password === this.password_confirmation) {
this.$http.plain
.post("/signup", {
email: this.email,
password: this.password,
password_confirmation: this.password_confirmation
})
// If successful execute signinSuccesful
.then(response => this.signinSuccesful(response))
// If it doesn't run for whatever reason, execute signupFailed
.catch(error => this.signinFailed(error));
}
},`
This should in theory create a POST request that would be received by the API, I have attempted to catch the request like this: post "signup", controller: :signup, action: :create
However, when I look in the console on chrome I get an error when I first load the site:
`0/signin net::ERR_CONNECTION_REFUSED`
And another error when I click the button to send the POST request:
*
Access to XMLHttpRequest at 'http://localhost:3000/signup' from origin
'http://localhost:8080' has been blocked by CORS policy: Request
header field content-type is not allowed by
Access-Control-Allow-Headers in preflight response.
My 'application.rb' file looks like this:
`module RecordstoreBackend
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.0
config.api_only = true
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :patch, :put, :delete, :post, :options]
end
end`
I think the issue is this file, but I am not sure how to configure it. Thanks for your help.
It seems to me like you're missing the host for that request (on your Vue app). Notice the error says 0/signup which indicates that it's sending the request to http://0/signup that is, in turn, "rejecting" the connection.
I'm not familiar with Vue.js structure, but I'd recommend encapsulating the axios call on a plugin that would use the host from an environment configuration file, so that you can have different hosts for localhost and your production env. Or even just add the host to your environment:
methods: {
// Performs a POST request to the rails endpoint and passes the email and pass as parameters
signup() {
if (this.password === this.password_confirmation) {
this.$http.plain
.post(`${process.env.API_HOST}/signup`, {
email: this.email,
password: this.password,
password_confirmation: this.password_confirmation
})
// If successful execute signinSuccesful
.then(response => this.signinSuccesful(response))
// If it doesn't run for whatever reason, execute signupFailed
.catch(error => this.signinFailed(error));
}
},
Reference: https://cli.vuejs.org/guide/mode-and-env.html#environment-variables
If you want to fix it the way you have it now, simply add your host to the axios call:
methods: {
// Performs a POST request to the rails endpoint and passes the email and pass as parameters
signup() {
if (this.password === this.password_confirmation) {
this.$http.plain
.post("http://localhost:3000/signup", {
email: this.email,
password: this.password,
password_confirmation: this.password_confirmation
})
// If successful execute signinSuccesful
.then(response => this.signinSuccesful(response))
// If it doesn't run for whatever reason, execute signupFailed
.catch(error => this.signinFailed(error));
}
},
About the CORS error, check this: https://stackoverflow.com/a/25727411/715444
I have a react component in my rails app where I'm trying to use fetch() to send a POST to my rails app hosted on localhost, this gives me the error:
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
I'm using devise gem to handle user/registrations and logins.
I have tried to remove protect_from_forgery with: :exception
Here is the code for my fetch,
this.state.ids.sub_id});
fetch(POST_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: body
}).then(res => res.json()).then(console.log);
How can I get the csrf token and send it through the form so that it will pass?
Ideally I would like to just send it through the headers but I have no idea how to access the token.
The simplest way to do this, if you are merely embedding a react component in your Rails view, is to retrieve the csrf token from your rails view and then pass it as a header in your fetch api call.
You can get the csrf token by doing something like this:
const csrf = document.querySelector("meta[name='csrf-token']").getAttribute("content");
And then you just pass it as a header in your fetch call:
...
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf
},
...
I normally don't use fetch, so not too sure about the exact syntax, but this should help guide you.
Thanks! I ended up with this as a working solution:
In the view that renders my react component
<% csrf_token = form_authenticity_token %>
<%= react_component('ExerciseDisplay', {
program: #program.subprograms.first.exercises, ids: {sub_id: #program.subprograms.first.id, team_id: #team.id, token: csrf_token}
}) %>
I passed the token into state, then accessed it via fetch:
fetch(POST_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': this.state.ids.token
},
body: body
}).then(res => res.json()).then(console.log);
Inside your form element, add a authenticity_token:
<input type="hidden" name="authenticity_token" value={csrf}/>
Set the value as per the below:
const csrf = document.querySelector("meta[name='csrf-token']").getAttribute("content");
I too faced the same issue when using rails 5.2 and i was able to fix this issue by adding
protect_from_forgery with: :null_session in application_controller.rb i got this from here,please refer this
I have made method in rails controller which returns json with session variable:
def get_facebook_code
render json: { code: session[:facebook_auth_code] }
end
When I paste link in browser http://localhost:3000/credentials/facebook/code it works fine - session variable returns
And session variable is null when I run request from Postman or from js code from third-party application:
$.ajax({
type: "GET",
url: "http://localhost:3000/credentials/facebook/code",
success: function(msg) {
console.log(msg);
},
});
I allow requests for credentials/* path with help of rack-cors gem
resource '/credentials/*', headers: :any, methods: [:get]
Because by default rails stores sessions in cookies.
Somehow you have added that key 'facebook_auth_code' in your browser session. The same key is not available to postman/third party app requests because cookies are different.
Long time lurker, first time poster here.
There are many good guides and resources about JWTs and how and where to store them. But I'm running into an impasse when it comes to securely storing and sending a JWT between a ReactJS/Flux app running on a Node server and a completely separate Rails API.
It seems most guides tell you to just store the JWT in local storage and pluck it out for every AJAX request you make and pass it along in a header. https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/ warns against this, however, since local storage is not secure and a malicious person could access that token. It recommends storing it in the cookie instead and just letting the web browser pass it along with each request.
That sounds fine to me since from what I understand cookies get conveniently sent along with every request anyway. It means I can just make AJAX requests from my ReactJS app to my Rails API and have the API pluck it out, check it, and do it's thing.*
The problem I'm running into is my Node application doesn't set a cookie from the response it gets back from the Rails API even though the Rails API (running on localhost:3000) returns a Set-Cookie header and sends it back to the ReactJS/Node app (running on localhost:8080).
Here's my login controller action on my Rails API side:
class V1::SessionsController < ApplicationController
def create
user = User.where(email: params[:user][:email]).first!
if user && user.authenticate(params[:user][:password])
token = issue_new_token_for(user)
# I've tried this too.
# cookies[:access_token] = {
# :value => token,
# :expires => 3.days.from_now,
# :domain => 'https://localhost:8080'
# }
response.headers['Set-Cookie'] = "access_token=#{token}"
render json: { user: { id: user.id, email: user.email }, token: token }, status: 200
else
render json: { errors: 'username or password did not match' }, status: 422
end
end
end
The gist of it is it takes an email and password, looks the user up, and generates JWT if the info checks out.
Here's the AJAX request that is calling it from my Node app:
$.ajax({
url: 'http://localhost:3000/v1/login',
method: 'post',
dataType: 'json',
data: {
user: {
email: data.email,
password: data.password
},
callback: '' //required to get around ajax CORS
},
success: function(response){
console.log(response);
},
error: function(response) {
console.log(response);
}
})
Inspecting the response from the Rails API shows it has a Set-Cookie header with a value of access_token=jwt.token.here
Screenshot:
Chrome Dev Tools Inspector Screenshot
However, localhost:8080 does not show any cookies set and subsequent AJAX calls from my Node/React app do not have any cookies being sent along with them.
My question is, what piece(s) am I misunderstanding. What would I have to do to make storing JWTs in cookies work in this scenario?
A follow-up question: assuming storing the JWT in a cookie is not an option, what potential security risks could there be with storing the JWT in local storage (assuming I don't put any sensitive info in the JWT and they all expire in some arbitrary amount of time)?
*this may be a fundamental misunderstanding I have. Please set me straight if I have this wrong.
Side-notes that may be of interest:
My Rails API has CORS setup to only allow traffic from localhost:8080
in development.
In production, the Node/React app will probably be
running on a main domain (example.com) and the Rails API will be
running on a sub domain (api.example.com), but I haven't gotten that
far yet.
There's nothing sensitive in my JWT, so local storage is an
option, but I want to know why my setup doesn't work with cookies.
Update elithrar submitted an answer that worked:
I needed to modify my AJAX request with xhrFields and crossDomain as well as tell jQuery to support cors:
$.support.cors = true;
$.ajax({
url: 'http://localhost:3000/v1/login',
method: 'post',
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
data: {
user: {
email: data.email,
password: data.password
}
},
success: function(response){
console.log(response);
},
error: function(response) {
console.log(response);
}
})
And I added credentials: true and expose: true to my Rack Cors configuration on my Rails API (the * is only for my development environment):
config.middleware.insert_before 0, 'Rack::Cors' do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :path, :options], credentials: true, expose: true
end
end
I have an Ember CLI app that uses Rails as a backend. The code appears to be fine, and if I visit localhost:3000, it displays the JSON output just fine. However, Ember does not display this data.
Here is what I have so far:
// ember/app/adapters/application.js
import DS from "ember-data";
export default DS.ActiveModelAdapter.extend({
namespace: 'api'
});
// ember/app/controllers/data_keys.js
export default Ember.ArrayController.extend({
sortProperties: ['name']
});
// ember/app/models/data_key.js
export default DS.Model.extend({
name: DS.attr('string')
});
// ember/app/routes/data_keys/index.js
export default Ember.Route.extend({
controllerName: 'data_keys',
model: function() {
return this.store.all('data_key');
}
});
// rails/app/controllers/api/data_keys_controller.rb
class Api::DataKeysController < ApplicationController
def index
render json: DataKey.all
end
end
// rails/app/serializers/data_key_serializer.rb
class DataKeySerializer < ActiveModel::Serializer
attributes :id, :name
end
// rails/config/routes.rb
Rails.application.routes.draw do
namespace :api do
resources :data_keys
end
end
To run the app, I run the Rails side with rails s, then in another terminal tab I run ember server --proxy http://localhost:3000.
Is there something obvious that I'm missing? I know that Ember CLI version 0.0.39 had proxy uses, but I'm using Ember CLI 0.0.40.
ember -v
version: 0.0.40
node: 0.10.28
npm: 1.4.21
Here's an example of the JSON returned by the server:
{
"data_keys": [{
"id": 1,
"name": "foo"
}, {
"id": 2,
"name": "bar"
}]
}
I think that when you use DS.Store#all you're only filtering the records that are already in the store. You'll want to use DS.Store#find in order to get the records from the server.
export default Ember.Route.extend({
controllerName: 'data_keys',
model: function() {
return this.store.find('data_key');
}
});
Also, your ember files should use dashes rather than underscores.