Im using Vue.js 3 & the vue-cookies package Vue Cookies
this is how im setting the cookies in the app
in main.js
import VueCookies from "vue-cookies";
app.use(VueCookies, { expires: "35min" });
in login.vue
const $cookies = inject("$cookies");
const handleLogin = async () => {
try{
const res = await axios({
method: "POST",
url: "/sellers/login",
data: {
Email: email.value,
Password: password.value,
},
// withCredentials: true,
});
let token = res.data.token;
// $cookies.set("jwt", token); //---->method 1
const storeToken = useLocalStorage("token", {
token: res.data.token,
});
await new Promise((resolve) => {
setTimeout(resolve, 3000);
});
let storedToken = JSON.parse(window.localStorage.getItem("token"));
$cookies.set("jwt", storedToken.token); ///---> method 2
console.log("getting the cookie");
let y = $cookies.get("jwt");
console.log(y);
}
catch(error){
}
i've tried storing the cookie in local storage then retrieving and setting it from there (method 2) because i thought the problem was method 1
the results of console.log(y) is null
however,i have confirmed the token is in local storage
Both Method 1 & 2 work when the app is running via the Vite development server
After building for production and serving the assets in dist with nodejs, it does not work
what im i doing wrong or haven't done?
i can see the response from the node.js server and even save them in localstorage (like the token)
i can also retrieve the token from localstorage
setting the token from localstorage (method 2) or setting it direct from the response (method 1) is what is not happening
i appreciate your help
First of all, you shouldn't create authentication cookies or save any kind of authorization token this way. It makes a good vulnerability for XSS attacks. You should try to set the cookie from your backend with the httpOnly attribute which is not possible from the client side.
Regarding your problem, it's quite difficult to say what could be the problem on production. My best guess is that your production environment is using https and your cookie is being set insecurely by the package you are using as its the default. Therefor, it is only accessible when using http which you are probably using for development.
Try to set the config to use secure cookies when your import.meta.env.PROD equals true like this example below:
$cookies.config('35m', '', '', import.meta.env.PROD)
You should also make sure that the correct domain is set so it's accessible from the client.
Related
I am implementing a login feature to a website project. The backend is Express and the frontend is Nuxt 3. Upon successfully authenticating a user login, the Express backend returns necessary data to the webserver, which then creates an httpOnly cookie and sets any necessary data in a Pinia store. On page refresh, I would like the Nuxt 3 server to look at the cookie and setup the Pinia store (since it is lost on page refresh).
Can someone provide some guidance? I have looked at the useNuxtApp() composable, and I can see the cookie in nuxtApp.ssrContext.req.headers.cookie, but that only provides a K/V pairing of all set cookies, which I would need to parse. I know of the useCookie composable, but that only works during Lifecycle hooks, which seems to only resolve undefined.
Thanks.
Not sure if this is the right way,
but it's a solution I used to get through a similar case - dotnet api + nuxt3 client.
First, we need to proxy API (express in your case),
this will make it, so our cookie is on the same domain and browser will start sending it to /api/ endpoints.
Install #nuxtjs-alt/proxy - npm i #nuxtjs-alt/proxy.
Add configuration to nuxt.config.ts (my api running on localhost:3000):
nuxt.config.ts:
export default defineNuxtConfig({
modules: [
'#nuxtjs-alt/proxy'
],
proxy: {
enableProxy: true,
fetch: true,
proxies: {
'/proxy': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/proxy/, '')
}
}
}
});
Then we can the request that will set a cookie anywhere on client using proxy instead of a direct call.
Anywhere on client, do a request using newly setup proxy instead of calling API directly.
Adjust parameters based on your setup.
await $fetch('/proxy/user/sign-in', {
method: 'POST',
body: {
email: 'example#mail.com',
password: 'password'
}
});
Ultimately, should end up with a cookie set on our client domain.
And lastly, when we handle request client side - we read the cookie and set up on forwarding request.
Replace COOKIE_NAME and API URL accordingly.
server/api/user/me.get.ts:
export default defineEventHandler(async (event) => {
return await $fetch('http://localhost:3000/user/me', {
headers: {
Cookie: `COOKIE_NAME=${
getCookie(event, 'COOKIE_NAME')
}`
}
});
});
API call will use the same cookie we got when we did a first request using cookie and the server should be able to read it.
Front-End: [Axios]
const formSubmit = async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const email = formData.get('email')
const password = formData.get('password')
try {
const res = await axios.post('http://172.16.2.19:3001/api/v1/auth/login', {
email,
password,
})
console.log(res.data) // its okay, I can login if email & password are correct.
} catch (error) {
console.log(error)
}
}
Back-End [Nodejs ExpressJs]:
Inside App.js:
const cors = require('cors')
app.use(cors({ credentials: true }))
Inside Login.js (/auth/login endpoint):
// ... code, then... if email & password are correct:
// 3600000ms = 1hour
res.cookie('jwt', token, { httpOnly: true, expires: new Date(Date.now() + 3600000 })
res.status(200).json({
status: 'success'
token,
data: userDoc,
})
Then, when I login in my browser:
I can login successfully, but no cookies will be created, see:
The front-end http service (react app) is running on http://172.16.2.19:3000
The back-end http service (expressjs) is running on http://172.16.2.19:3001
The axios requests I'm sending from the front-end are requesting: http://172.16.2.19:3001
So what's the problem?
The problem that no cookies are getting created in the browser is preventing me from continuing to design the front-end application, because if I wanted to request anything from my API, I have to be authenticated, all the routes on the API I made are protected, so if I wanted to request anything from the API, I will have to send my jwt token along with the request.
edit **:
here's the response from the /auth/login endpoint after successfully logging in:
I am using brave browser, the latest version.
I tried this on firefox, it has the same behavior.
GUYS GUYS GUYS I found it!!!! after 3 hours of researching, let me save your time:
For anyone having the same problem, all you have to do is
change your backend code from:
const cors = require('cors')
app.use(cors({ credentials: true }))
to
app.use(cors({ credentials: true, origin: true }))
and make sure you're using withCredentials: true on the front-end with every request (the login POST method and all the other requests that requires authentication)
why?
setting origin property to true is going to reflect the request origin, the origin property can be a string if you wanted to specify a particular domain, ex: http://localhost:3000. But if you have more than one client, setting this to true is a wise choise.
and for those of you wondering about mobile devices in case of specifying a string for the origin field with one particular domain. This problem of cors only happens in browsers, any other client doesn't use that CORS policy.
I would check by passing {withCredentials: true} as the third argument to the axios method to allow the browser to set the cookie via the request.
I don't think it is correct to use the backend to save cookies, as cookies is a browser feature separate from the database. I might be wrong though. When the post is successful, res will return a token. You save this token in the browser's local storage.
const formSubmit = async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const email = formData.get('email')
const password = formData.get('password')
try {
const res = await axios.post('http://172.16.2.19:3001/api/v1/auth/login', {
email,
password,
})
//browsers local storage
localStorage.setItem('access_token',res.data.access);
localStorage.setItem('refresh_token',res.data.refresh);
console.log(res.data) // its okay, I can login if email & password are correct.
}
You will then have to create an authorization header as such
headers:{
Authorization: localStorage.getItem('access_token')
? 'JWT '+localStorage.getItem('access_token')
: null
}
I currently have a application with Laravel + Sanctum + Vue SPA + Apollo GraphQL.
I'm trying to make a session expire just like in a normal Laravel application but i can't achieve this.
First I make a request to trigger the csrf-cookie of Sanctum on frontend:
await fetch(`${process.env.VUE_APP_API_HTTP}/api/csrf-cookie`, {
credentials: 'include'
})
It generates 2 cookies on browser:
XSRF-COOKIE and laravel_session
On login I use apollo and store the auth-token after make a login request:
const data = await apolloClient.mutate({
mutation: Login,
variables: credentials
})
const token = data.data.login.token
await onLogin(apolloClient, token)
export async function onLogin (apolloClient, token) {
if (typeof localStorage !== 'undefined' && token) {
localStorage.setItem(AUTH_TOKEN_NAME, token)
}
....
So i pass the token and cookie to apolloClient link prop, but i'm not sure if it is needed to pass the XSRF-TOKEN.
const authLink = setContext(async (_, { headers }) => {
const token = localStorage.getItem(AUTH_TOKEN_NAME)
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
'XSRF-TOKEN': Cookie.get('XSRF-TOKEN'),
}
}
})
Here is the problem: The login session never expires, even with the cookie laravel_session, i already tried to pass laravel_session as a header on my link connection but it doesn't seems to work.
My Laravel session.php is set 'expire_on_close' => true to be sure i can test it i close the browser and re-open, also i'm sure the cookie is set to expire on close because it says on browser cookies info.
Any idea how can i make the laravel session work on a SPA?
If you are using cookies to manage the session, your .env file should look like this:
SESSION_DRIVER=cookie
You can also define the session lifetime below
SESSION_LIFETIME=120
Suggestion: set lifetime to 1 minute, do a login and wait to see if it expires. Let me know!
I'm developing a fullstack app with Node + Express backend and NextJS front end (separate servers) and am having trouble requesting the browser to attach the cookie vended down as part of the response header from the node server. Here's the setup:
Node server is running on localhost:3000 and NextJs server is running on localhost:3001.
I have set up alias in etc/hosts to route someAlias.com to 127.0.0.1.
Using the front end UI (port 3001) I was able to vend the cookie with JsHttp's cookie module with the following code from the backend (port 3000):
import { serialize } from 'cookie';
...
const cookie = serialize(TOKEN_NAME, TOKEN_VAL, {
httpOnly: true,
sameSite: 'none',
});
I was able to observe the Set-Cookie header in the response.
However, in the subsequent requests, I did not see the cookie being attached. I have tried fiddling with the above cookie serialization params with no success:
Here are the arguments I've tried:
domain: ['.someAlias.com:3000', '.someAlias.com:3001']
path: '/'
domain: '.someAlias.com'
I have a feeling it might just be due to front end and back end server ports being different, but all requests have been initiated on the client side going to localhost:3000 (backend port). So not sure what I've possibly done wrong here.
====== UPDATE =======
I've run a couple more experiments, and found out that when I'm accessing a URL directly, NextJs renders the page server-side. When I'm transitioning between pages within the app, the page is rendered client-side where it queries the backend port 3000 directly. Unfortunately in neither scenario did I see any cookie being set...
the cookies must be sent directly from the browser to the server , which is not the case when you use nextJs . because when you access to your app next js will server side render your page and then the request will be sent from nextjs server to your nodejs server so the browser will send the cookies to nextjs server not to your nodejs server .
the solution is to send cookies manually from nextjs server to nodejs .
example with fetchApi and getServerSideProps function:
export async function getServerSideProps(context){
try{
const res = await fetch(`your-api-endpoint`, {
method: 'GET',
credentials:'include',
headers: {
'Access-Control-Allow-Credentials': true,
Cookie: context.req.headers.cookie
},
})
const data = await res.json()
if(!res.ok){
throw data
}
}catch(err){
return console.log(err)
}
return {
props: {
data
}
}
}
SAMESITE NONE
If you use SameSite=none you also need to use Secure, meaning SSL must also be used. You are then saying that your 2 servers are from unrelated domains, which is probably not what you want, since browsers will drop cookies aggressively.
LOCAL PC OPTIONS
Use these settings initially on your development computer, and ensure that all URLs used in the browser and Ajax calls use http://somealias.com:3000 and http://somealias.com:3001 rather than localhost. Cookies will then stick on a development computer.
Domain=.somealias.com
Path=/
SameSite=strict
HTTP Only
DEPLOYED OPTIONS
When you deploy to a proper environment, also use SSL and set the cookie option Secure. Most importantly, the two domains must meet hosting prerequisites of sharing the same base domain, eg:
https://www.example.com
https://api.example.com
This ensures that cookies issued are considered first party and in the same site, so that they are not dropped. If preconditions are not met, there is nothing you can do in code to fix the problem.
SIMILAR RESOURCE
This Curity code example uses local development domains and same site cookie settings similar to those I've used above and may be useful to compare against.
You should set serialized cookie with res.set Express method.
Alternatively, you can use res.cookie method without additional cookie package like this:
res.cookie(TOKEN_NAME, TOKEN_VAL, {
httpOnly: true,
sameSite: 'none',
});
Note, you shouldn't worry about different ports on the same domain, since cookies are not isolated by port but domain only. No matter what port you use, cookies should be visible.
Since you said, "I was able to observe the Set-Cookie header in the response", I believe your node.js setting correct.
When you get response from node js, you need to set cookies, which can be done with a npm packages easily. I will demonstrate with js-cookie:
import Cookies from "js-cookie";
you write a reusable function to set the cookies:
// what ever your project returns
setSession(authResult) {
//converting everything to miliseconds
const expiresAt =
JSON.stringify(authResult.expiresIn * 1000) + new Date().getTime();
// I just put properties. I dont know how project sets
Cookies.set("user", authResult.idTokenPayload);
Cookies.set("jwt", authResult.idToken);
Cookies.set("expiresAt", expiresAt);
}
Everytime you make request you have to set headers. You have to retrieve cookies based on if you are on browser or on server. So you have to write a function if you are on server. Since I demonstrated how to set cookies with js-cookies, you can get the cookies easily on the browser. This reusable function to retrieve the cookie if you are on the server:
// cookieKey: I set three "user", "jwt","expiresAt"
export const getCookieFromReq = (req, cookieKey) => {
console.log("req.headers", req.headers);
// cookies are attached to the req.header.cookie.
const cookie = req.headers.cookie
.split(";")
.find((c) => c.trim().startsWith(`${cookieKey}=`));
if (!cookie) return undefined;
return cookie.split("=")[1];
};
Now you have to write a function to set the headers:
import Cookies from "js-cookie";
import { getCookieFromReq } from "./directoryOf";
export const setAuthHeader = (req) => {
const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");
if (token) {
return {
headers: { authorization: `Bearer ${token}` },
};
}
return undefined;
};
Now when you make request, you have to use this setAuthHeader. For example:
await axiosInstance
.post("/blogs", blogData, setAuthHeader())
.then((response) => response.data)
.catch((error) => rejectPromise(error));
I'm writing an Azure function that takes an OAuth token from Microsoft, which I've been able to successfully obtain. I'm trying to use that token to access to Microsoft Graph. After I receive the token from Microsoft my function times-out after ten minutes and doesn't get past context.log('CALLING MS GRAPH'.) I'm new to Azure and haven't been able to figure out why I can't call my second function with the value of the token returned from Microsoft or with a hard coded value.
Any help is greatly appreciated :)
I've tried hardcoding the token value into the function, changing the timeout, and adding various context.log()'s - but can't get past receiving the token. I've also tried removing the .end() to my POST call.
const https = require('https');
const querystring = require('querystring');
getAccessToken = (context, callback) => {
const postData = querystring.stringify({
'client_id': {clientID},
'scope': 'https://graph.microsoft.com/.default',
'client_secret': {clientSecret},
'grant_type': 'client_credentials'
});
const msTokenOptions = {
hostname: 'login.microsoftonline.com',
port: 443,
path: `/${tenantID}}/oauth2/v2.0/token`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
}
};
const oauthReq = https.request(msTokenOptions, (res) => {
res.setEncoding('utf8');
res.on('data', (d) => {
let accessToken = JSON.parse(d).access_token;
// Error happens here.
context.log('CALLING MSGRAPH')
// I never make it into the functions below, regardless of how they're called.
callback(accessToken);
accessMsGraph(accessToken)
});
});
oauthReq.on('error', (e) => {
context.log('ERROR: Problem obtaining MS Token. ' + e);
});
oauthReq.write(postData);
oauthReq.end();
return;
};
accessMsGraph = (token) => {
// GET request to MS Graph here - I never make it into this function.
};
module.exports = (context, req) => {
getAccessToken(context, (token) => {
context.log('Accessing graph')
accessMsGraph(context, token)
accessMsGraph('123456')
});
};
Please check the Access token lifespan which has been set in your tenant.
This actually isn't determined by Microsoft Graph but rather by Azure Active Directory.For a given tenant, the life-time can be configured using Configurable token lifetimes in Azure Active Directory (Public Preview).
This functionality is still in Preview, so functionality may change between now and general release.
This configuration is per tenant, service principal, or application. If you configure it on the application, then the policy will apply on multi-tenant applications unless superseded by a policy on the service principal or tenant level.
The maximum lifetime for an Access token is 24 hours (minimum is 10 minutes, default is 1 hour).
In general, rather than adjusting the lifetime of the Access Token you should rely on the Refresh Token instead. These have a much longer lifetime of 14 days.
Refresh Token
When a client acquires an access token to access a protected resource, the client also receives a refresh token. The refresh token is used to obtain new access/refresh token pairs when the current access token expires. A refresh token is bound to a combination of user and client. A refresh token can be revoked at any time, and the token's validity is checked every time the token is used. Refresh tokens are not revoked when used to fetch new access tokens - it's best practice, however, to securely delete the old token when getting a new one.