Safari doesn't set cookie on subdomain - cookies

I've the following setup:
local domain entries in /etc/hosts:
127.0.0.1 app.spike.local
127.0.0.1 api.spike.local
I've created an express server in TypeScript:
const app = express()
app.use(cookieparser())
app.use(
cors({
origin: 'https://app.spike.local',
credentials: true,
exposedHeaders: ['Set-Cookie'],
allowedHeaders: ['Set-Cookie']
})
)
app.get('/connect/token', (req, res) => {
const jwt = JWT.sign({ sub: 'user' }, secret)
return res
.status(200)
.cookie('auth', jwt, {
domain: '.spike.local',
maxAge: 20 * 1000,
httpOnly: true,
sameSite: 'none',
secure: true
})
.send()
})
type JWTToken = { sub: string }
app.get('/userinfo', (req, res) => {
const auth = req.cookies.auth
try {
const token = JWT.verify(auth, secret) as JWTToken
console.log(req.cookies.auth)
return res.status(200).send(token.sub)
} catch (err) {
return res.status(401).json(err)
}
})
export { app }
I've created a simple frontend:
<button
id="gettoken"
class="m-2 p-1 rounded-sm bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-opacity-50 text-white"
>
Get Token
</button>
<button
id="callapi"
class="m-2 p-1 rounded-sm bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-opacity-50 text-white"
>
Call API
</button>
<div class="m-2">
Token Response Status Code:
<span id="tokenresponse" class="bg-green-100"></span>
</div>
<div class="m-2">
API Response: <span id="apifailure" class="bg-red-100"></span
><span id="apiresponse" class="bg-green-100"></span>
</div>
<script type="text/javascript">
const tokenresponse = document.getElementById('tokenresponse')
const apiresponse = document.getElementById('apiresponse')
const apifailure = document.getElementById('apifailure')
document.getElementById('gettoken').addEventListener('click', async () => {
const response = await fetch('https://api.spike.local/connect/token', {
credentials: 'include',
cache: 'no-store'
})
tokenresponse.innerHTML = response.status
})
document.getElementById('callapi').addEventListener('click', async () => {
const userInfoResponse = await fetch('https://api.spike.local/userinfo', {
credentials: 'include',
cache: 'no-store'
})
if (userInfoResponse.status === 200) {
const userInfo = await userInfoResponse.text()
apifailure.innerHTML = ''
apiresponse.innerHTML = userInfo + ' #' + new Date().toISOString()
} else {
const failure = (await userInfoResponse.json()).message
console.log(failure)
apiresponse.innerHTML = ''
apifailure.innerHTML = failure
}
})
</script>
When running the UI on https://app.spike.local and the API on https://api.spike.local both using self certificates and browsing the UI, I can successfully request a token in a cookie and subsequently use this token via cookie being sent automatically for the API call in Chrome and Firefox.
However, on Safari on macOS (and iOS) the Cookie isn't being sent in the subsequent API call.
As can be seen,
Cookie settings are SameSite=None, HttpOnly, Secure, Domain=.spike.local.
CORS has no wildcards for headers and origins and exposes and allows the Set-Cookie header as well as Access-Control-Allow-Credentials.
on client side, fetch options include credentials: 'include'
As said, both API and UI are served over SSL with valid self signed certificates.
When disabling Preferences/Privacy/Prevent cross-site tracking in Safari, everything works fine. But this not an option for this scenario in production.
What am I doing wrong here?

Solved it by changing the TLD to .com instead of .local.
The hint has been in this comment.

Related

while i login cookies are stored but while delete cookie in onclick event on logout button it gives cors error i also used cors as middileware

Frontend code
const callLogout = async () =>{
try{
let res = await fetch('http://localhost:8000/logout',{
method:"GET",
headers:{
Accept:"application/json",
"Content-Type":"application/json"
},
credentials:"include"
})
let data = res.json()
navigate('/login',{replace:true})
if (res.status!==200){
const error = new Error(res.error)
console.log(error)
}
}
catch(err){
console.log(err)
}
}
<button onClick={callLogout} className='btn btn-primary' style={{position: 'absolute', right: '38px'}}>
Logout
</button>
Backend code
router.get('/logout' , (req , res) =>{
res.clearCookie('jwttoken' , {path:'/'})
return res.status(200)
})
the code is working properly when i login but when i click on logout button it gives cors error
Access to fetch at 'http://localhost:8000/logout' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
About.js:10 GET http://localhost:8000/logout net::ERR_FAILED

Token not being persisted across Cypress tests

I have created a Cypress command to fetch me a JWT token from my GQL API. I then set the token as a cookie to use in my tests that require authentication.
Here is my login command:
export function login(username, password) {
const { apiUrl: url } = Cypress.env();
cy.request({
method: 'POST',
url,
body: {
operationName: 'signin',
headers: {
'content-type': 'application/json'
},
query:
`mutation signin($email: String!, $password: String!) {
signin(input: {email: $email, password: $password}) {
id
token
__typename
}
}`,
variables: { email: username, password: password }
}
})
.then(response => {
const { token } = response.body.data.signin;
cookie.set('token', token, { expires: 10000000 });
cy.wait(3000);
});
}
I can see when I run the login command the cookie is set but when my test tries to visit a page within my app the cookie disappears.
describe('Entity Page', () => {
before(() => {
const { username, password } = Cypress.env();
cy.login(username, password);
cy.addEntity(newEntityId, {});
cy.wait(3000);
cy.get('#entityId').then(entityId => {
cy.visit(`/entity/${entityId}`)
cy.wait(6000)
});
});
By the time I get to addEntity the cookie disappears and I am unauthenticated. Is there something I need to do to persist cookies? I tried Cypress.Cookies.preserveOnce but this had no effect
I also tried adding the below to my support/index.js but the cookie is still removed.
Cypress.Cookies.defaults({
preserve: 'token'
})
Try it with cy.setCookie(name, value) docs.
It has a couple of defaults that might help.
domain - defaults to window.location.hostname. The domain the cookie is visible to.
expiry - defaults to 20 years into the future. Specified as seconds since 1 January 1970 (10,000,000 is only 115 days).

VueApollo CORS request fails

i'm facing a problem trying to make a request to API with address different against client.
client app lives at http://localhost:8080
server app lives at http://localhost:4000
in main.js i'm creating apollo client
const apolloClient = new ApolloClient({
uri: 'http://localhost:4000/v1/graphql',
})
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
and feed the apolloProvider variable to Vue.
in component code that's calling API endpoint is looking like this
<template>
<div>{{ categories }}</div>
</template>
<script>
import gql from 'graphql-tag'
export default {
apollo: {
categories: gql`query {
categories {
name
_id
}
}`
}
}
</script>
my GraphQL server that should accept the query from VueApollo is looking like this
// apollo
const { ApolloServer, makeExecutableSchema } = require('apollo-server')
const typeDefs = require('./schema')
const resolvers = require('./resolvers')
const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
const server = new ApolloServer({
schema,
cors: {
origin: 'http://localhost:8080',
methods: 'POST',
optionsSuccessStatus: 204,
preflightContinue: false,
},
})
server.listen({ port: process.env.PORT || 4000 }).then(({ url }) => {
console.log(`🚀 app running at ${url}`)
})
in Chrome browser requests from VueApollo accepted and response returned appropriately, but in FireFox i'm getting a CORS errors like this
am i missing anything guys? please help!
i'm not sure what was wrong, but while i was trying to find out a solution i've noticed that my vue-cli module out of date. for me it was 3.1.1. so i updated vue cli to 4.5.9 and it got worked.

how to set cookies during vuejs post

I am trying to send post data to a django Restful API using vuejs. here is the code I have so far:
<script>
import axios from 'axios'
import VueCookies from 'vue-cookies'
//3RD ATTEMPT
VueCookies.set("csrftoken","00000000000000000000000000000000");
// # is an alias to /src
export default {
name: "Signup",
components: {},
data: () => {
},
methods: {
sendData(){
// 2ND ATTEMPT
// $cookies.set("csrftoken", "00000000000000000000000000000000");
axios({
method: 'post', //you can set what request you want to be
url: 'https://localhost:8000/indy/signup/',
data: {
csrfmiddlewaretoken: "00000000000000000000000000000000",
first_name: "wade",
last_name: "king",
email: "wade%40mail.com",
password1: "05470a5bfe",
password2: "05470a5bfe"
},
// 1ST ATTEMPT
// headers: {
// Cookie: "csrftoken= 00000000000000000000000000000000"
// },
withCredentials: true
})
}
}
</script>
I have a button which executes the sendData() method on a click. The code uses the axios library to send a post request to the django API running on http://localhost:800/indy/signup/
The problem with just sending a post request to the API is that it will get blocked in order to prevent Cross Site Response Forgery (CSRF), I dont quite understand CSRF but I know if the csrftoken is set as a cookie and has the same value as the csrfmiddlewaretoken then the post should go through to the API.
You can see my attempts to set the cookie in the code I provided
1ST ATTEMPT)
headers: {
Cookie: "csrftoken= 00000000000000000000000000000000"
},
Here I'm trying to set the cookie directly in the header. When I click send I get an error in my browser console saying refused to set unsafe header "Cookie"
2ND ATTEMPT)
$cookies.set("csrftoken", "00000000000000000000000000000000");
Here I'm trying to set the cookie using the vue-cookies module. When i click send I get the following error, net::ERR_SSL_PROTOCOL_ERROR
3RD ATTEMPT)
VueCookies.set("csrftoken","00000000000000000000000000000000");
Here I'm trying to set a global cookie using the vue-cookies module. When I click send I get the same error as attempt 2
IMPORTANT:
However when I send post data to the API from my terminal using the following curl command, it works perfectly
curl -s -D - -o /dev/null \
-H 'Cookie: csrftoken= 00000000000000000000000000000000' \
--data 'csrfmiddlewaretoken=00000000000000000000000000000000&first_name=wade&last_name=king&email=wade%40mail.com&password1=05470a5bfe&password2=05470a5bfe' \
http://localhost:8000/indy/signup/
my main question is How can I replicate this curl request using vuejs? I've looked all over on line and none of the tutorials deal with setting cookies.
I posted this question some time ago, I have managed to work around it by running the vue frontend on the same network as the django backend. Follow this tutorial for instructions: integrating vuejs and django
Once I had the application set up I was able to set the cookies much more cleanly using :
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
Here is my login page for example
<template>
<div class = "container">
<h2>Sign In</h2>
<b-form v-on:submit.prevent="submit()">
<b-form-group id="signin" label="">
<!-- dynamic error message -->
<p class="loginErr" v-if="logErr">Incorrect Username or Password</p>
<b-form-input
id="signin-email"
v-model="username"
placeholder="Email"
required
></b-form-input>
<b-form-input
id="signin-password"
v-model="password"
placeholder="Password"
required
type="password"
></b-form-input>
</b-form-group>
<b-button v-if="!loading" type="submit" variant="primary">Submit</b-button>
<b-spinner v-if="loading"></b-spinner>
</b-form>
</div>
</template>
<script>
import axios from 'axios'
import Vue from 'vue'
export default {
data: ()=>{
return{
loading: false,
logErr: false,
username:'',
password:'',
next: '%2Findy%2Fprofile%2F'
}
},
created: function(){
},
methods: {
submit(){
var vm = this;
vm.loading = true;
var dataStr = 'username='+vm.username+'&password='+vm.password
//set the csrf tokens so django doesn't get fussy when we post
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
axios.post('http://localhost:8000/api/signin/', dataStr)
.then(function (response) {
vm.loading = false;
//determine if indy accepts the login request
var res = response.data
console.log(response.data)
if(!res.login){
vm.logErr = true;
}else{
vm.redirect('home');
}
})
.catch(function (error) {
//currentObj.output = error;
});
},
redirect(path) {
this.$router.push('/' + path);
}
}
}
</script>
<style>
.loginErr{
color: orange;
}
</style>

Apify: Preserve headers in RequestQueue

I'm trying to crawl our local Confluence installation with the PuppeteerCrawler. My strategy is to login first, then extracting the session cookies and using them in the header of the start url. The code is as follows:
First, I login 'by foot' to extract the relevant credentials:
const Apify = require("apify");
const browser = await Apify.launchPuppeteer({sloMo: 500});
const page = await browser.newPage();
await page.goto('https://mycompany/confluence/login.action');
await page.focus('input#os_username');
await page.keyboard.type('myusername');
await page.focus('input#os_password');
await page.keyboard.type('mypasswd');
await page.keyboard.press('Enter');
await page.waitForNavigation();
// Get cookies and close the login session
const cookies = await page.cookies();
browser.close();
const cookie_jsession = cookies.filter( cookie => {
return cookie.name === "JSESSIONID"
})[0];
const cookie_crowdtoken = cookies.filter( cookie => {
return cookie.name === "crowd.token_key"
})[0];
Then I'm building up the crawler structure with the prepared request header:
const startURL = {
url: 'https://mycompany/confluence/index.action',
method: 'GET',
headers:
{
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
Cookie: `${cookie_jsession.name}=${cookie_jsession.value}; ${cookie_crowdtoken.name}=${cookie_crowdtoken.value}`,
}
}
const requestQueue = await Apify.openRequestQueue();
await requestQueue.addRequest(new Apify.Request(startURL));
const pseudoUrls = [ new Apify.PseudoUrl('https://mycompany/confluence/[.*]')];
const crawler = new Apify.PuppeteerCrawler({
launchPuppeteerOptions: {headless: false, sloMo: 500 },
requestQueue,
handlePageFunction: async ({ request, page }) => {
const title = await page.title();
console.log(`Title of ${request.url}: ${title}`);
console.log(page.content());
await Apify.utils.enqueueLinks({
page,
selector: 'a:not(.like-button)',
pseudoUrls,
requestQueue
});
},
maxRequestsPerCrawl: 3,
maxConcurrency: 10,
});
await crawler.run();
The by-foot-login and cookie extraction seems to be ok (the "curlified" request works perfectly), but Confluence doesn't accept the login via puppeteer / headless chromium. It seems like the headers are getting lost somehow..
What am I doing wrong?
Without first going into the details of why the headers don't work, I would suggest defining a custom gotoFunction in the PuppeteerCrawler options, such as:
{
// ...
gotoFunction: async ({ request, page }) => {
await page.setCookie(...cookies); // From page.cookies() earlier.
return page.goto(request.url, { timeout: 60000 })
}
}
This way, you don't need to do the parsing and the cookies will automatically be injected into the browser before each page load.
As a note, modifying default request headers when using a headless browser is not a good practice, because it may lead to blocking on some sites that match received headers against a list of known browser fingerprints.
Update:
The below section is no longer relevant, because you can now use the Request class to override headers as expected.
The headers problem is a complex one involving request interception in Puppeteer. Here's the related GitHub issue in Apify SDK. Unfortunately, the method of overriding headers via a Request object currently does not work in PuppeteerCrawler, so that's why you were unsuccessful.