How to expire a session in Laravel SPA "laravel_session" cookie?" - javascript

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!

Related

Setting Cookies does not work in production Vuejs, vuecookies

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.

How can I use refresh token in react

I have a get refresh token url like this client.com/api//auth/refresh-token. but I have a hard time using this. I think it should save a refresh token in the local storage after the login. but how can I use it?
login.tsx
export const useLogin = () => {
const LoginAuth = async (data: AuthenticationProps) => {
await axios.post(baseURL + `client/auth/login`,
{
email: data.email,
password: data.password,
},
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
}
}
)
.then((res) => {
if(res.status === 200) {
console.log("success")
}
}, (err) => {
console.log(err);
})
}
return {
LoginAuth,
}
}
You should not set the refresh token in local storage, it would cause a security vulnerability, since local storage is accessible by javascript, and since refresh token is long term token (live longer than access token), what you would do is, to store access token in local storage, since access token is short termed token, storing in local storage or cookies is totally fine, and then you should make an useEffect() call in react, that check whenever the token is expired and then make the call, a small example:
import Cookies from 'js-cookie';
axios.get("ur_url_here/",data,{withCredentials:true}).then((res)=>{
Cookies.set(res.data.access) // assuming the response has the access token
}))
// now we check the expiration of access token
useEffect(()=>{
if(!(Cookies.get("access"))){
axios.get("refresh_url_here/",{withCredentials:true}).then((res)=>{
Cookies.set(res.data.access)
})
/*what you do here, is try to have a
resource/view in your backend that has
the refresh token and make request to it
so that it gives you a new access token,
because refresh token should be in cookies tagged with `httponly',
then you can send the access token to client side
as a response and set it somewhere.
*/
}
else{
//do something else
}
},[])
this is a simplified code, but should explain well the idea of refreshing a token safely.
also note, i stored access in cookies, but you can do the same and store it in local storage.
Save it in local storage
export const storeToken = async (token: string) => {
await AsyncStorage.setItem('#token', token);
};
And fetch from storage when needed
export const getToken = async () => {
return await AsyncStorage.getItem('#token');
};
You should probably fetch the token from storage when application starts or when fetching from the API and store it in state or such while using the application.
Save in web storage
Only strings can be stored in web storage
LocalStorage
Persists even when the browser is closed and reopened.
Get
const token = localStorage.getItem('token');
Set
localStorage.setItem('token', 'value')
SessionStorage
Data removed when browser closed
Get
sessionStorage.getItem('token', 'value')
Set
sessionStorage.setItem('token', 'value')
You can use LocalStorage, or SessionStorage.
export const useLogin = () => {
const LoginAuth = async (data: AuthenticationProps) => {
await axios.post(baseURL + `client/auth/login`,
{
email: data.email,
password: data.password,
},
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
}
}
)
.then((res) => {
if(res.status === 200) {
console.log("success")
window.localstorage.setItem('authToken', res.data.token);
// Same as session storage
// window.localstorage.setItem('authToken', res.data.token);
}
}, (err) => {
console.log(err);
})
}
return {
LoginAuth,
}
}
You can check here for the difference
The best way to store the refresh token is in localstorage.
Setting token in localstorage,
localStorage.setItem("token", token);
Getting token from localstorage
let token = localStorage.getItem("token");
You can also view the stored token in browser like below,
All the measures of security of web application logic process conclude by giving you access token and refresh token, then and its your responsibility to keep them safe. As long as these tokens are valid, they are only artifacts required to make an access. In fact if you look at OIDC flows the access token is not even handed to browsers in most of them because of so many known weaknesses of browsers in terms in security.
The best way to keep or store either of these tokens is to deal with them in back channel and if not then in browsers encrypt them with custom logic and store in local storage so that only your app knows how to use these tokens.
Better even to have the backend code does this part for you as you know javascript is always exposed and retrievable.
Hope this helps.

Axios doesn't create a cookie even though set-cookie header is there?

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
}

Most ideal way to call firebase getIdToken

i am implementing user authentication with the help of firebase in my React project. So, I am confused over something.
I am verifying the user from firebase and then getting a token on frontend which is sent to backend via headers and verfied there once.
I read the docs and came to know that firebase token gets expired after 1 hr by default so we have to use "getIdToken" like
firebase.auth().onAuthStateChanged(async user => {
if (user) {
console.log(user, 'user123 inside firebaseAuth')
const token = await user.getIdToken()
Cookies.set('my_token', token, { domain: domain })
}
})
but how do i manage this function , do i have to call it everytime the component updates or everytime before hitting api or first time the component renders ?
The thing is i do not want this token to get expire until the user logs out himself / herself even if he is in a different component and sitting ideal for too long.
You can get the Firebase ID Token every time you are making an API call to your server:
async function callAPI() {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken()
const res = await fetch("url", {
headers: {authorization: `Bearer ${token}`}
})
} else {
console.log("No user is logged in")
}
}
You could get the ID token once when the component mounts but then you'll have to deal with onIdTokenChanged to keep it updated in your state. Using the method above you'll get a valid token always.

How to get JWT cookies in our react application, how to check the user is login or not I am unable to find how to handle my react application session

How to get JWT cookies in our react application, how to check the user is login or not I am unable to find how to handle my react application session.
I really appreciate who helps me out with this problem.
Thanks in advance
The server side API is setting the HTTPOnly cookie which you wont be able to read in JS.
What you need to do it in your react App handle a 401 status error and based on that set a flag isAuthenticated or something as false. Otherwise keep it to be true. With each request to the server HTTPOnly cookie would be sent automatically so you don't need to handle the token inside a cookie.
The backend code needs to send a 401 once the cookie is expired, or the logout is requested or the JWT inside a cookie expires.
Before I say anything, you have included app.use(cookieParser()) in index.js right? Because if not, you're gonna need that once you've installed it with npm i cookie-parser
But anyway, a few things:
You can create a PrivateRoute in React, as far as I'm aware this tends to work well to protect routes from unauthorized users.
You can simply store an isAuthenticated in either the state or localStorage: however this will require that you make absolutely sure that a user shouldn't be able to just change the value in the state or add isAuthenticated in localStorage and just spoof authenticity (this is the part that took me the longest to get right).
Anyway, the way I've handled this is that when a user logs in an access token (and a refresh token if it doesn't already exists) are generated from the server and sent to the client in an httpOnly cookie, while this makes it so that you can't do anything with it on the client side with JavaScript as Pavan pointed out in his answer (which is generally a good thing), you should use res.status for validation when you make the fetch request. Here's what my fetch request kind of looks like:
const login = async (user) => {
const body = JSON.stringify(user);
return fetch(loginURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'include', //this is important
body: body
}).then(function(res) {
if (res.status === 200) {
const id = Date.now();
localStorage.sid = id; //this is temporary
return res.json()
} else {
return res.json()
}
})
// you can ignore this part
.then(async resp => {
return resp
})
}
Side-note: Your browser automatically handles the httpOnly cookies you send from your server however the credentials: 'include' needs to be included in your subsequent fetch requests.
In one of my main parent components, the login function is called:
login = async (user) => {
this.setState({ error: null });
await adapter.login(user).then(data => {
if (!data.error) {
this.setState({session: "active"})
} else if (data.error && data.error.status === 401) {
// this is so I can handle displaying invalid credentials errors and stuff
this.setState({ error: data.error, loading: false });
}
});
}
I also have a middleware on the server-side that is run before any of the code in the routes to verify that the user making the request is actually authorized. This is also what handles access token expiration for me; if the access token has expired, I use the refresh token to generate a new access token and send it back in the httpOnly cookie, again with a status of 200 so the user experience isn't jarring. This does of course mean that your refresh token would have to live longer than your access token (I haven't decided how long in my case yet, but I'm thinking either 7 or 14 days) though as far as I'm aware that is okay to do.
One last thing, the parent component I referred to earlier calls a validate function which is a fetch request to the server within its componentDidMount so that the user is verified each time the component mounts, and then I've included some conditional rendering:
render() {
return (
<div className="container">
{
!localStorage.sid && <LoginForms {...yourPropsHere}/>
}
{
this.state.loading && <Loading />
}
{
localStorage.sid && this.state.session === "active" && <Route path="/" render={(props) => <Root error={this.state.error} {...props}/>}/>
}
</div>
);
}
I've gone the conditional rendering route as I couldn't get PrivateRoute to work properly in the time that I had, but either should be fine.
Hopefully this helps.
Your login API should return JWT token and how long it should be live.
Your login API response would be like :
{
jwt: your jwt token,
duration: in seconds
}
Use universal-cookies NPM to store this result into cookies.
For more details how to manipulate with cookies, visit
https://www.npmjs.com/package/universal-cookie
For setting cookies your code like:
const cookies = new Cookies();
cookies.set(name of cookies, jwt value from API call, {
maxAge: duration,
});
Above code store the jwt cookies in browser and after maxAge automatically remove it from browser.
So for identifying session is present or not, you should check after specific interval cookies has present in browser or not. If cookies has present in browser then session is on, otherwise session has expired.

Categories

Resources