I am using react.js and Axios. For backend I am using express-sessions. In the login function, if the user successfully login, I set the res.session.userId with the relevant value (userId), and sent to frontend the userId.
/**
* If the credentials are correct and the user is accepted and not banned, set the session with the
* user's id and send status 200 (success)
*/
if (matchPassword && userRequested.accepted && userRequested.banned == false) {
req.session.userId = userRequested.userId;
return res.status(200).send({ userId:userRequested.userId });
}
The frontend, receive the session in the Cookie tab. However, the goal is to get the data from the session and use the data to redirect the user to profile page if it is authenticated and the session is not expired ... I need to get the userId from the cookie storage. I tried 'js-cookie', 'react-cookies'... But none of them works.
I also tried document.cookie but get undefined.
For the Axios configuration I used based URL and credentials mode to be set to true.
const FETCH_DATA = axios.create({
baseURL: 'http://localhost:3000/api',
withCredentials: true
});
export const loginUser = async (email, password) => {
const user = await FETCH_DATA.post('/login', {email, password});
console.log(user.status);
return user;
}
I need to get the userId from the session, but to be used on the frontend. So far I am get only in the browser but I am unable to work with it.
The session from the mysql table looks like this after the user loggedin:
{"cookie":{"originalMaxAge":86400000,"expires":"2022-10-31T09:36:49.418Z","secure":false,"httpOnly":true,"path":"/"},"userId":34}
Related
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 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.
I am dealing with firebase auth now and I was following this Firebase document.
Visit https://firebase.google.com/docs/auth/admin/manage-cookies#sign_in
// When the user signs in with email and password.
firebase.auth().signInWithEmailAndPassword('user#example.com', 'password').then(user => {
// Get the user's ID token as it is needed to exchange for a session cookie.
return user.getIdToken().then(idToken = > {
// Session login endpoint is queried and the session cookie is set.
// CSRF protection should be taken into account.
// ...
const csrfToken = getCookie('csrfToken')
return postIdTokenToSessionLogin('/sessionLogin', idToken, csrfToken);
});
})
I expected that I could get a token by using that function. But It doesn't work because user in the code doesn't have getIdToken() function.
Seems like things changed since 7.* version. To get it:
firebase.auth().signInWithEmailAndPassword('user#example.com', 'password').then(({ user }) => {
// Get the user's ID token as it is needed to exchange for a session cookie.
return user.getIdToken().then(idToken = > {
// Session login endpoint is queried and the session cookie is set.
// CSRF protection should be taken into account.
// ...
const csrfToken = getCookie('csrfToken')
return postIdTokenToSessionLogin('/sessionLogin', idToken, csrfToken);
});
})
Note, that you need to use user.user.getIdToken() now, or just use destructuring as I did in the example.
To get the id token, just call auth's currentUser#getIdToken directly.
const idToken = await firebase.auth().currentUser.getIdToken()
I have created a backend for user registration and login, I do not know how sessions are handled and verified in the back end.
I read some articles on how to generate the session token but I have no clue of how to validate that token once send to the server side asking for some information
this is what i did, stored the session in the backend for each user and then with a handmade middle-ware asked if this session is created for that user or not which i know is inefficient
router.post("/createUser",(req,res)=>{
const {Name, Email , Phone , Password, UserName} = req.body
console.log(Email,Phone,Password)
if(Name && Email && Phone && Password){
const user = new UserModel({Name,Email,Phone,Password,UserName})
user.save((e)=>e? console.log(e): console.log("success"))
const Session = new SessionModel({userID:user._id,session:req.sessionID})
Session.save()
res.status(201).send(req.sessionID)
}else{
res.status(500).send()
}
})
and this is how i validate the request
router.use("/profile",(req, res , next)=>{
const {SessionID , UserID} = req.query
SessionModel.findOne({userID:UserID},(err,session)=>{
if(session.session === SessionID){
next()
}else{
return res.status(500).send()
}
})})
router.get("/profile",(req,res)=>{
res.send("works")
})
You are quite duplicating things: express-sessions already manages sessions for you, there is no sense in duplicating those sessions into a database (express-sessions can do that for you if you have to scale beyond one server).
Actually you could just store the userID in the session, then check wether a userID exists in the session to validate the request. If you need to access the user data, you can just look the user up based on the id.
router.post("/createUser",(req,res) => {
// ...
req.session.userID = user._id;
//...
});
router.use((req, res, next) => {
if(!req.session.userID)
return res.status(403).send("not logged in");
next();
});
// all routes are secured beyond this point
Mandatory Note: Never ever store plain text passwords in your database (aka don't be like Facebook ;)). At least hash them, if you want to do it right hash them with a per user salt.
I'm developing a web application using Node.js/Express.js for the backend and I use Firebase for user authentication, and to manage user registration etc I use Firebase Admin SDK.
When a user want to login I sign him in using Firebase Client SDK like this:
// Handling User SignIn
$('#signin').on('click', function(e){
e.preventDefault();
let form = $('#signin-form'),
email = form.find('#email').val(),
pass = form.find('#password').val(),
errorWrapper = form.find('.error-wrapper');
if(email && pass){
firebase.auth().signInWithEmailAndPassword(email, pass)
.catch(err => {
showError(errorWrapper, err.code)
});
}else {
showError(errorWrapper, 'auth/required');
}
});
Below this code, I set an observer to watch for when the user successfully sign in, After a successfull sign in I get a Firebase ID token which I send to an endpoint on the server to exchange it for a session cookie that has the same claims the ID token since the later expires after 1 hour.
// POST to session login endpoint.
let postIdTokenToSessionLogin = function(url, idToken, csrfToken) {
return $.ajax({
type: 'POST',
url: url,
data: {
idToken: idToken,
csrfToken: csrfToken
},
contentType: 'application/x-www-form-urlencoded'
});
};
// Handling SignedIn Users
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
user.getIdToken().then(function(idToken) {
let csrfToken = getCookie('csrfToken');
return postIdTokenToSessionLogin('/auth/signin', idToken, csrfToken)
.then(() => {
location.href = '/dashboard';
}).catch(err => {
location.href = '/signin';
});
});
});
} else {
// No user is signed in.
}
});
Sign in endpoint on the server looks like this:
// Session signin endpoint.
router.post('/auth/signin', (req, res) => {
// Omitted Code...
firebase.auth().verifyIdToken(idToken).then(decodedClaims => {
return firebase.auth().createSessionCookie(idToken, {
expiresIn
});
}).then(sessionCookie => {
// Omitted Code...
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({
status: 'success'
}));
}).catch(err => {
res.status(401).send('UNAUTHORIZED REQUEST!');
});
});
I have created a middle ware to verify user session cookie before giving him access to protected content that looks like this:
function isAuthenticated(auth) {
return (req, res, next) => {
let sessionCookie = req.cookies.session || '';
firebase.auth().verifySessionCookie(sessionCookie, true).then(decodedClaims => {
if (auth) {
return res.redirect('/dashboard')
} else {
res.locals.user = decodedClaims;
next();
}
}).catch(err => {
if (auth) next();
else return res.redirect('/signin')
});
}
}
To show user information on the view I set the decoded claims on res.locals.user variable and pass it to the next middle ware where I render the view and passing that variable like this.
router.get('/', (req, res) => {
res.render('dashboard/settings', {
user: res.locals.user
});
});
So far everything is fine, now the problem comes after the user go to his dashboard to change his information (name and email), when he submits the form that has his name and email to an endpoint on the server I update his credentials using Firebase Admin SDK
// Handling User Profile Update
function settingsRouter(req, res) {
// Validate User Information ...
// Update User Info
let displayName = req.body.fullName,
email = req.body.email
let userRecord = {
email,
displayName
}
return updateUser(res.locals.user.sub, userRecord).then(userRecord => {
res.locals.user = userRecord;
return res.render('dashboard/settings', {
user: res.locals.user
});
}).catch(err => {
return res.status(422).render('dashboard/settings', {
user: res.locals.user
});
});
}
Now the view gets updated when the user submits the form because I set the res.locals.user variable to the new userRecord but once he refreshes the page the view shows the old credentials because before any get request for a protected content the middle ware isAuthenticated gets executed and the later gets user information from the session cookie which contains the old user credentials before he updated them.
So far these are the conclusions that I came to and what I tried to do:
If I want the view to render properly I should sign out and sign in again to get a new Firebase ID token to create a new session cookie which is not an option.
I tried to refresh the session cookie by creating a new ID token from the Admin SDK but it doesn't seem to have this option available and I can't do that through the client SDK because the user is already signed in.
Storing the ID token to use later in creating session cookies is not an option as they expire after 1 hour.
I Googled the hell out of this problem before posting here so any help is so much appreciated.
I am facing a very similar scenario with one of my apps. I think the answer lies in these clues.
From Firebase docs
Firebase Auth provides server-side session cookie management for traditional websites that rely on session cookies. This solution has several advantages over client-side short-lived ID tokens, which may require a redirect mechanism each time to update the session cookie on expiration:
So they're hinting here that you want to manage the session and it's lifetime from the server.
Second clue is in the docs
Assuming an application is using httpOnly server side cookies, sign in a user on the login page using the client SDKs. A Firebase ID token is generated, and the ID token is then sent via HTTP POST to a session login endpoint where, using the Admin SDK, a session cookie is generated. On success, the state should be cleared from the client side storage.
If you look at the example code, the even explicitly set persistence to None to clear state from the client using firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
So they are intending there to be no state on the client beyond the initial auth. They explicitly clear that state and expect an httponly cookie so the client can't grab the cookie (which really is just the ID token) and use it to get a new one.
It is odd that there is no clear way of refreshing the token client-side but there it is. You can only really create a session cookie with a super long lifetime and decide on the server when to delete the cookie or revoke the refresh token etc.
So that leaves the other option: manage state client-side. Some examples and tutorials simply send the ID token from the client to the server in a cookie. The satte sits on the client and the client can use the ID token to use all firebase features. The server can verify the user identity and use the token etc.
This scenario should work better. If the server needs to kick the user then it can delete the cookie revoke the refresh token (a bit harsh admittedly).
Hope that helps. Another scheme would be to build custom tokens, then you have complete control.