Is it possible to set an HttpOnly Cookie from one domain to another subdomain - authentication

I originally posted this question here: https://security.stackexchange.com/questions/255737/is-it-possible-to-set-an-httponly-cookie-from-one-domain-to-another-subdomain
Please keep in mind that this question is specific to cookies with the HttpOnly flag set to true.
I am pretty sure that the answer to my question is no, but I have been have a hard time finding an answer through official documentation or other posts here. Here is simple use case for some context:
Python backend web application (api.domain.com)
Frontend JavaScript SPA (app.domain.com)
post requests to api.domain.com/api/auth/login/ made from app.domain.com using axios with the correct username and password return a response with an access JWT token in the body and the response sets a refresh cookie with an HttpOnly flag [should fail, since I believe that the cookie cannot be set on app.domain.com from an API request to api.domain.com? -- this is my question]
the access token is stored in memory and passed with each API request
requests made to api.domain.com/api/auth/refresh/ are sent on a schedule to refresh the short-lived access token.
I typically host the frontend app and backend app on the same subdomain (app.domain.com) and do path-based routing with something like CloudFront or nginx, and this works well. For example, all requests starting with /api/* are sent to the backend, and all other requests are sent to the frontend app. Trying to use a separate subdomain for the API seems to fail no matter what options I use for setting the cookie on the server.
Can someone help me confirm that it is in fact not possible to set an HttpOnly cookie on a subdomain like app.domain.com from an API request hosted on api.domain.com? It would be great if anyone can also help me find where this could possibly be found in official documentation.
Searching for set httpOnly cookie across subdomains, I haven't found anything directly relevant. I also didn't find anything in these resources that directly answers my question:
https://owasp.org/www-community/HttpOnly
https://learn.microsoft.com/en-us/previous-versions//ms533046(v=vs.85)?redirectedfrom=MSDN

This is possible. In fact I just did it.
On your frontend, using Axios:
const baseURL = 'https://api.example.com';
const api = axios.create({
baseURL,
withCredentials: true,
});
On your backend, using Express:
app.use(
cors({
origin: 'https://www.example.com',
credentials: true,
}),
);
app.post('/login', async (req, res) => {
res.cookie('someCookie', someCookieValue, {
secure: true,
domain: 'example.com',
httpOnly: true,
});
});

Related

Cross domain axios call to pass cookies

I have a webapp hosted in heroku under https://xyz.herokuapp.com. I've made it so that when user hits https://xyz.herokuapp.com, it would drop a cookie under the domain "xyz.herokuapp.com".
This is the snippet I have on server side hosted in heroku that sets the cookie.
res.cookie('sample_cookie',randomNumber, {
maxAge: 900000,
httpOnly: true,
sameSite: 'none',
secure: true
});
Further I have CORS configured like so
app.use(cors({
origin: '*'
}));
Now I am building another web app hosted in say abc.netlify.com. Here I have a simple react app that uses axios to call an endpoint under xyz.herokuapp.com. Under this case, I expect during the GET call that goes out to xyz.herokuapp.com from abc.netlify.com, the cookies set by xyz.herokuapp.com to be passed along. But I am not sure how to achieve this.
Here is my axios call made from abc.netlify
axios.get('https://xyz.herokuapp.com/getInfo', {crossDomain:true});
How can I make sure the cookies set under xyz.herokuapp.com gets passed even when the request comes from cross domain like this example. Thanks for your help.

Putting a react-django staging site behind basic auth, but auth clashes with token auth for endpoints

Whats the situation?
I've got staging site which is built with Django + React.
Parts of the API you have to login to access. I'm using Django's token authentication for that.
I then wanted to put the entire site behind basic auth, to prevent anyone of accidentally stumbling across it.
What's the problem?
This means I need to pass two authentication methods with my requests. This is possible as described here.
Authorization: Token lksdjf893kj2nlk2n3rl2dOPOnm, Basic YXNkZnNhZGZzYWRmOlZLdDVOMVhk
The token is set in my JS code after being provided to the user when they login in.
Basic authentication is triggered on the first page load, after this the browser stores it and I believe automatically appends it onto any requests where the server has the following header:
WWW-Authenticate: basic
I have configured Django to return the following header:
WWW-Authenticate: basic, token
This successfully causes a XHR request sent via axios to have the basic header appended, when the Authorization header is empty.
The problem is the Authorization header isn't empty, because I need to set a token value in there.
const axiosConfig = {
method: requestType,
url: `${url}`,
withCredentials: true,
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
data: payload
};
// If we're logged in then send our auth token
if (localStorage.auth_token) {
// Axios can't see the basic authentication header here so we can't append.
console.log(axiosConfig.headers.Authorization);
// Basic auth will only get sent if I don't set anything here
axiosConfig.headers.Authorization = `Token ${localStorage.auth_token}`;
}
At this point the browser doesn't seem to append the basic header anymore and so my authentication fails.
Is there a way around this?
I wanted a blanket basic auth because there's no way I can accidentally expose anything on staging, otherwise I have to rely entirely on the token authentication and robots.txt which is less than ideal.
The answer in the end was port forwarding.
I removed basic auth, turned off ports 80 and 443 and then used port forwarding to map my SSH to local host.
i.e. ssh -N -L 8755:127.0.0.1:443 user#ip_address

how to skip Preflight Requset in vue with content-type:application/json

error :"405 not allowed Method" in post method type call in request command vue
i need call api function with content-type:application/json and post Method type with request command in vue ,but browser add preflight request with options method type and it causes this error :"405 not allowed Method"
var options = {
method: "POST",
url: "http://api.sample.com/login",
headers: {
"Access-Control-Request-Method":"POST",
"cache-control": "no-cache",
"content-type": "application/json",
},
body: '{ Username: "demo", Password: "demo", Domain: "test" }'
};
request(options, function(error, response, body) {
if (error) throw new Error(error);
body.data;
alert("ok");
});
The OPTIONS call is done whenever you do a cross-origin request. This means the domain your application is running on is different from the domain where the api is. A pre-flight request is mandatory for these requests, because the browser needs to figure out if you are allowed to do these requests. A 405 error means that the server thinks you are not allowed to make that request.
To solve this problem you can move your api to the same domain as your frontend. Please note that it cannot be on a subdomain.
A different way of solving this, is by sending back the correct headers. In your case you seem to at least miss the Access-Control-Allow-Methods response header. Make sure to send this header and either dynamically figure out which methods are allowed, or do something like the following. That would allow the most common methods to work.
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
In the comments you said that you do not have control over the api, and as such cannot change the response header. In that case your best bet is to contact whoever maintains the api and ask how to best use their api.
In the comments you said that this worked fine when you did the same thing in ASP.NET. ASP.NET is a server-side language, which means that requests in that context do not have a concept of "cross-origin". Cross-origin only comes into play in the browser, where the application runs on an actual domain.
Assuming you can set up a proxy on your application domain, you can also create a proxy that proxies all requests to the api you actually want to communicate with. You would deploy your domain on https://example.com and do your requests to https://example.com/api/endpoint. Your proxy will listen for requests that begin with https://example.com/api and proxy it to https://whatever.the.api.is/ with the appropriate endpoint and data.
Please keep in mind that while some api's might just be configured incorrectly, a lack of cross-origin response headers might just mean that the api is nog meant to be consumed through the browser. Part of this could be that the request contains a secret that should not be exposed to users that use your application, but should instead only be on the server. Using a proxy in that case would set you up for impersonation attacks, because you would expose the secret to your application, but defeat the cross-origin headers by making it appear to the application that the api is on the same domain.

Authentication in GraphQL servers

How to properly handle authentication in GraphQL servers?
Is it ok to pass a JWT token at the Authorization header of query/mutation requests?
Should I use something from GraphQL specification?
Stateless solutions is preferable.
Thanks.
A while ago I was wondering the same thing for sometime,
but apparently authentication is out of the scope of what GraphQL is trying to accomplish (see the conversations on Github).
But there are solutions such as this which handles it with sessions.
Assuming you use express-graphql, here is what you can do.
import graphQLHTTP from 'express-graphql'
app.use(`/graphql`, [aValidationFunction, graphQLHTTP(options)])
function aValidationFunction(req, res, next) {
const { authorization } = req.headers
// Do your validation here by using redis or whatever
if (validUser) {
return next()
} else {
return res.status(403)
}
}
It depends on whether your GraphQL consumer is a webapp or mobileapp.
If it is a webapp, then I would recommend sticking with session-cookie-based authentication since most popular web frameworks support this, and you also get CSRF protection.
If it is a mobileapp, then you will want JWT. You can try manually getting a cookie header from login response, and put stuff this "cookie" in your next request, but I had problem that some proxy servers strip off this "cookie", leaving your request unauthenticated. So as you said, including JWT in every authenticated request (GraphQL request) is the way to go.

How do I get cross-site XMLHttpRequest invocations to send credentials using Sails?

I am trying to get a cross-origin front-end to behave correctly with a SailsJS back-end hosted at another domain. Specifically, the user authentication and credentials are what is broken.
Everything behaves correctly with the normal POST, GET, etc type of database behavior. For example, registration of new users already works.
However, after the user is logged in, the authentication step fails and I receive a 401 Unauthorized.
Here is what the console is telling me:
[Object, "post", "login", "https://small-change-api.herokuapp.com/user/login"]
User logged in: Object {firstName: "John", lastName: "Does", email: "test#work.com", activated: true, superUser: falseā€¦}
-- GOOD :)
[Array[0], "get", "authenticated", "https://small-change-api.herokuapp.com/user/authenticated"]
GET https://small-change-api.herokuapp.com/user/authenticated 401 (Unauthorized)
-- Not good
Now, all of this works 100% locally, but hosted on Heroku, something is going wrong :/
Here is some relevant server-side configuration:
This is in the file config/cors.js
module.exports.cors = {
allRoutes: true,
origin: 'https://thefrontendorigin.com',
credentials: true,
methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH',
headers: 'content-type,Access-Control-Allow-Origin'
};
In the front-end withCredentials set to true:
RestangularProvider.setDefaultHttpFields({ withCredentials: true });
What else do I need to get right? It seems like it should work... :/
Let me know if I need to include any more information!
Thanks.
P.S. Both GH repos are public right now, so let me share those.
Front-end Github
Back-end Github
What I needed to do, was go into the CORS settings (Cross-Origin Requests) and add this line:
headers: 'content-type,Access-Control-Allow-Credentials'
That is the crucial bit I was missing, hope that helps somebody else.