Rails: session variable is nil outside - ruby-on-rails

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.

Related

Using Rails, how should I configure my application.rb in order to allow for CORS requests to take place?

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

Can't verify CSRF token authenticity Rails/React

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

Which gatsby-source plugin to use with a Rails api using graphql gem

I'm building a Rails API using the gem 'graphql' and want to access this GraphQL API using a Gatsby.js front-end. I've attempted to use gatsby-source-apiserver plugin and gatsby-source-graphql, but neither of them seem to work. (The API works fine when I do queries using the GraphiQL app on my local machine.)
Is there a better Gatsby.js source plugin what will work well with a Rails API using the gem 'graphql', which provides a single endpoint, http://localhost:3000/graphql? And if so, how should I configure that plugin in gatsby-config.js, etc? (BTW, I'm using a Postgres database which I intend to deploy to Heroku -- I'd thought about pursuing hasura, but I'm not sure if that's the best option.)
In the Rails API in routes.rb, I've set up post "/graphql", to: "graphql#execute" to route to GraphqlController. Here is the execute method in the Controller...
def execute
variables = ensure_hash(params[:variables])
query = params[:query]
operation_name = params[:operationName]
context = {
# Query context goes here, for example:
# current_user: current_user,
}
result = GraphqlRailsSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
render json: result
rescue => e
raise e unless Rails.env.development?
handle_error_in_development e
end
When I attempt to render the json: result, result is: #<GraphQL::Query::Result #query=... #to_h={"errors"=>[{"message"=>"No query string was present"}]}> for either of the front-end set-ups I cite below....
In my Gatsby.js front-end, when I use gatsby-source-apiserver, in gatsby-config.js, I have...
{
resolve: 'gatsby-source-apiserver',
options: {
typePrefix: 'internal__',
url: `http://localhost:3000/graphql`,
method: 'post',
headers: {
'Content-Type': 'application/json'
},
data: {
},
name: `posts`,
entityLevel: `data.posts`,
payloadKey: `body`,
}
},
...and I get his error in my console when I run gatsby develop...
TypeError: Cannot read property 'data' of undefined
And, when I use gatsby-source-graphql, I have this in gatsby-config.js...
{
resolve: "gatsby-source-graphql",
options: {
// This type will contain remote schema Query type
typeName: "Authors",
// This is field under which it's accessible
fieldName: "authors",
// Url to query from
url: "http://localhost:3000/graphql",
},
},
...and I get his error in my console when I run gatsby develop...
Error: Cannot find module 'gatsby/graphql'
As you can see, I'm confused about how to connect the schemas between the front-end and the back-end here. Any help on this would be much appreciated!

How can I have a Rails API set a JWT in a cookie on a client that is running on another domain?

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

Rails Request Content-Type Seems incorrect when accessing API with AngularJS

I have a straightforward Rails 4.1.4 application and I'm trying to connect an AngularJS application on a separate host to it's API. Whilst I'm have no problem accessing it, Rails seems to think the request is HTML and ignores the Content-Type: 'application/json'
Started GET "/api/v1/locations?access_token=xxx&headers=%7B%22Content-type%22:%22application%2Fjson%22%7D" for 127.0.0.1 at 2014-09-03 17:12:11 +0100
Processing by Api::V1::LocationsController#index as HTML
Parameters: {"access_token"=>"xxx", "headers"=>"{\"Content-type\":\"application/json\"}"}
And in my NG application, I've tried a number of combinations of headers including:
app.factory('Location', ['$resource', "$localStorage",
function($resource, $localStorage){
return $resource('http://my-api.com/api/v1/locations/', {headers:{'Content-type' : 'application/json'}}, {
query: {
method: 'GET',
headers: {'Content-type': 'application/json'},
isArray: true,
dataType: 'json',
params: { access_token: $localStorage.accessToken }
}...
The response looks ok on the NG side, it's responding with JSON despite only having this in my locations controller:
class Api::V1::LocationsController < Api::V1::BaseController
doorkeeper_for :all
before_filter :authorize
respond_to :json
def index
#locations = #current_user.locations.order("created_at desc").limit(5)
respond_with #locations
end
end
I have also set (and tested unset) the cors headers.
I read somewhere that Rails won't read the content-type header if there's forward slashes in it...
Whilst this doesn't appear to be causing many issues, I do think it's interfering with Doorkeeper that's part of the application.
This wasn't a Rails problem. Turns out I needed to fiddle with some headers etc. in the NG config.
I added the following to app.js
$httpProvider.defaults.useXDomain = true;
// $httpProvider.defaults.withCredentials = true;
delete $httpProvider.defaults.headers.common["X-Requested-With"];
$httpProvider.defaults.headers.common["Accept"] = "application/json";
$httpProvider.defaults.headers.common["Content-Type"] = "application/json";
The second line threw an error but I've left in there for good measure.

Resources