Cookie Not Being Set on Netlify Hosted React App - reactjs

I have a React app that calls into a Rails App.
The React app is hosted on Netlify, and the Rails app on Heroku.
Locally, I am able to create a session and have the rails app set a cookie in the browser.
The network requests work just fine. However, after deploying to Netlify the production app isn't setting cookies.
Here is my login function
fetch(`${API_ROOT}/api/sessions`, {
headers: {'Content-Type': 'application/json'},
method: 'post',
credentials: 'include',
withCredentials: true,
body: JSON.stringify(body)
})
.then(response => response.json())
.then(data => {
if (data.status === 'created' && data.user){
props.handleLogin(data)
navigate("/");
} else
{
setAuthErrorState({
isError: true,
message: data.message
});
}
});
}
And my session_store.rb and cors.rb files
cors (domain redacted)
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins "http://localhost:3000"
resource "*", headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true
end
allow do
origins "https://www.[domain].io"
resource "*", headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true
end
end
session_store.rb
Rails.application.config.session_store :cookie_store, key: "_authentication_app"

I would request you to go to the "Network" tab in the dev tools of your browser and go to the request :'${API_ROOT}/api/sessions', and and confirm 2 things in the response headers of this request:
Is your React app domain name(i.e,: "https://www.[domain].io") present in the header Access-Control-Allow-Origin of the response? If not, what is the header set to ?
Is the said cookie present in the Cookie header of the response ? If not, please mention.
Purpose of this activity: To pinpoint the issue to either a CORS red flag by the browser , or the cookie not having the proper domain name set in it, and hence the cookie might appear to vanish.

This post helped me resolve the issue:
https://medium.com/#ArturoAlvarezV/use-session-cookies-to-authenticate-a-user-with-a-rails-api-backend-hosted-in-heroku-on-one-domain-f702ddf8c07

Related

Cross-site cookie not set in split-stack Rails 7/React 18 app

I have a Rails 7 API and React 18 front end, both deployed to Heroku on separate subdomains. Locally, the Rails app runs on localhost:3000 and the React app on localhost:3001. I need to set a CSRF cookie on the front end from the back end. I'm using the rack-cors gem on the Rails side to handle cross-origin requests. The CSRF cookie is set on the server side when the server gets a request from the front end to create an authenticated session. When the server verifies the authentication token (received by the front end from Google, it is supposed to set a CSRF-TOKEN cookie that will then be set in the browser and included by the front end in subsequent authenticated requests. I can see that the Set-Cookie header is being sent with the following value, but the cookie is not being set in Chrome in my dev environment (the site won't load in Firefox or Safari for other reasons, so I'm unable to test cookie settings in other browsers right now):
Set-Cookie: CSRF-TOKEN=c2Cn8OMs4IhgI5A4g1GC1XjG5hEc6RRW7dSPynxNbgsb0vsoWCr07yulWVzUwFYNP7dD8ARMps3pz5MMngKdog; path=/; secure; SameSite=None
My CORS initializer (/config/initializers/cors.rb) - configatron.client_origin is set to http://localhost:3001 in dev environments or https://sim.danascheider.com in prod):
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins configatron.client_origin
resource '*',
headers: :any,
methods: %i[get post put patch delete options head],
credentials: true
end
end
Rails.application.config.action_controller.forgery_protection_origin_check = false
The code that sets the cookie (this works based on inspecting the response in the dev tools):
cookies['CSRF-TOKEN'] = {
value: form_authenticity_token,
domain: :all,
same_site: :none,
secure: true
}
And the code that makes the request from the front end:
export const logInUser = token => {
const uri = `${backendBaseUri}/sessions`
const body = JSON.stringify({ token })
return(
fetch(uri, { method: 'POST', crossDomain: true, body, headers: { 'Content-Type': 'application/json' } })
.then(resp => {
if (resp.status === 401) throw new AuthorizationError()
return resp
})
)
}
I've looked in various other sources and all say to do what I'm already doing.
The cookie ended up being set when I added the credentials: 'include' option to my fetch request. I don't fully understand why this worked or was required on this request, but it did:
export const logInUser = token => {
const uri = `${backendBaseUri}/sessions`
const body = JSON.stringify({ token })
return(
// Replace crossDomain: true (not a real option for 'fetch') with credentials: 'include'
fetch(uri, { method: 'POST', credentials: 'include', body, headers: { 'Content-Type': 'application/json' } })
.then(resp => {
if (resp.status === 401) throw new AuthorizationError()
return resp
})
)
}

Browser is not saving cookie

I am creating a React app with a Go server. I set the cookie on the login request's response with http.cookie, which sends it back as seen in the network tab. But the browser doesn't save it. Tried with Chrome and Firefox. What am I doing wrong?
// Cors handler
r.Use(cors.Handler(cors.Options{
AllowOriginFunc: AllowOriginFunc,
AllowedMethods: []string{"GET", "POST", "DELETE"},
AllowedHeaders: []string{"*"},
AllowCredentials: true,
}))
func AllowOriginFunc(r *http.Request, origin string) bool {
if origin == "http://localhost:3000" || origin == "http://127.0.0.1:3000" {
return true
}
return false
}
// End of Login route sending back the token
userDetails := types.User{Name: user.Name, Email: user.Email, Profile_Pic: user.Profile_Pic}
cookie := &http.Cookie{Name: "accessToken", Value: token, MaxAge: int(maxAge), Path: "/api", HttpOnly: true, SameSite: http.SameSiteLaxMode}
http.SetCookie(w, cookie)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(userDetails)
Edit: Screenshots of the network tab.
Response headers
Request headers
Anybody else who comes across a similar problem, and is using the Fetch API, try setting 'credentials: "include"' in your fetch request that is EXPECTING A COOKIE IN THE RESPONSE. The browser then set the cookie it got in the response.
I had the wrong assumption that the 'credentials' flag must be set for requests that occur after the cookie is received. Finally working. Can't believe I spent 12 hours on setting a cookie smh.
fetch(`${url}/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include", // This here
body: JSON.stringify({
email: userDetails.email,
password: userDetails.password,
}),
}).then((response) => { ...
please try to put you cookie in header filed:"Set-Cookie".
e.g:
w.Header().Set("Set-Cookie","cookieName=cookieValue")
Make sure the response header has this field, and check it in you browser devtools.

CSRF cookie not set [Django/AngularJS]

First of all, this is not a duplicate question. I have tried all the related posts on stackoverflow but could not find a solution.
I have a Django app for the backend and and AngularJS app for the frontend. I am using djangorestframework-jwt for API authentication. I have an API endpoint that does not require any authentication and I am getting CSRF Failed: CSRF cookie not set error on the browser only for this endpoint.
In my django settings I have:
ALLOWED_HOSTS = ['*']
and it does not include any CSRF_COOKIE_SECURE, CSRF_COOKIE_HTTPONLY, or SESSION_COOKIE_SECURE settings.
The djangorestframework-jwt settings is:
JWT_AUTH = {
'JWT_SECRET_KEY': SECRET_KEY,
'JWT_ALGORITHM': 'HS256',
'JWT_VERIFY': True,
'JWT_VERIFY_EXPIRATION': True,
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3000),
'JWT_ALLOW_REFRESH': True,
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_AUTH_COOKIE': 'refresh-token'
}
I noticed that in the browser cookies if there is any refresh-token key then the endpoint works just fine. The problem arises when that key is not present in the browser cookies. I set 'JWT_AUTH_COOKIE': None or removed the following lines:
'JWT_ALLOW_REFRESH': True,
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_AUTH_COOKIE': 'refresh-token'
from the JWT_AUTH settings but no luck.
I also tried #csrf_excempt for that endpoint but that did not work either.
Interestingly, when I send the request from postman it works.
Here is the request I am sending from the frontend:
$http({
url: url,
method: "PUT",
headers: {
'Content-Type': 'application/json'
},
data: data
})
I would like to know why I am getting the error when refresh_token key is not present in the browser cookies and how to solve this issue.
I solved my issue by adding 'X-CSRFToken': $cookies.get("csrftoken") to the Http request header, so the request now look like:
$http({
url: url,
method: "PUT",
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': $cookies.get("csrftoken")
},
data: data
})

Sending requests with credentials

I made an authentication api made with ruby on rails 5, and a react js frontend. It works by taking an email and password, and sending cookies back to the the browser. However when I try to make the request, the console shows the following error:
Access to XMLHttpRequest at 'http://localhost:3000/users/login' from origin 'http://localhost:3001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
#react component - signin.js
axios(
`http://localhost:3000/users/login`, {
method: "post",
withCredentials: true,
data: {email: this.state.email, password: this.state.password}
})
.then(response => {
console.log(response)
})
.catch(error => console.log(error))
}
#rails login function
def login
#fetch user
user = User.find_by(email: params[:email].to_s.downcase)
#authenticate method
if user && user.authenticate(params[:password])
#disallow unconfirmed emails
if user.confirmed_at?
auth_token = JsonWebToken.encode({user_id: user.id})
##
cookies.signed[:jwt] = {value: auth_token, httponly: true}
render json: {auth_token: auth_token, email:user.email},
status: :ok
else
render json: {error: 'Email not verified' }, status:
:unauthorized
end
else
render json: {error: 'Invalid username / password'}, status:
:unauthorized
end
end
I think You should use rack cors.
Add gem 'rack-cors' in your rails app Gemfile
gem 'rack-cors'
Add below code to config/application.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'example.com'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Instead of 'example.com' in origins, you can provide your origin endpoint or use '*' to allow all.

CORS Preflight error with Apache, Laravel and AngularJS

I have implemented a backend with an Apache server with AMI from AWS and Laravel. For authentication I use the JWT Auth plugin.
My frontend is build with AngularJS. Before using the authentication everything worked fine. When I try to authenticate the user with an authorization header I get a CORS Preflight error. I use the following call from my AngularJS application:
delete $http.defaults.headers.common['X-Requested-With'];
$http.defaults.headers.common.Accept = "text/plain";
$http({
url: 'http://MYURL',
method: "GET",
headers: {
'Access-Control-Allow-Headers': 'Authorization, Content-Type, Accept',
'Content-Type' : 'text/plain',
'Authorization': 'Bearer '+token,
}
})
In my Laravel backend I used the following configuration:
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Authorization, Content-Type");
This is the response from the OPTIONS call:
This is the error I get in Google Chrome:
Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response.
Any ideas on this issue? Do I have to configure this within Angular, Laravel or my httpd.conf?
EDIT:
I added it as a global Middleware and in the app.php as service provider.
The configuration looks like this:
return [
'supportsCredentials' => false,
'allowedOrigins' => ['*'],
'allowedHeaders' => ['Authorization, Content-Type'],
'allowedMethods' => ['*'],
'exposedHeaders' => [],
'maxAge' => 0,
'hosts' => [],
];
But I have no idea if it works correctly.
Have you considered using a plugin for managing CORS setup like this one?
It appears that the list of headers you allow on the server side (Authorization, Content-Type) is not the same as the list of headers being sent by the request (Authorization, Content-Type, Accept). It could be that the front end is asking for permissions that you aren't allowing on the back end.

Resources