I'm implementing SSO authentication through passport google oidc strategy. Everything works fine for authentication, but I encounter some problems while logging out.
So, here's my Backend logout route
authRouter.post('/logout', async (req, res, next) => {
req.logout(req.user, (err)=> {
if (err) return next(err);
})
req.session.destroy((err) => {
res.clearCookie('connect.sid');
});
})
Here's Logout Handler on FrontEnd:
const handlerLogOut = async () => {
await axios.post('/api/auth/logout', {withCredentials: true})
.then(res => {
console.log('response ', res);
})
}
But while destroying the session, I encounter the next problem which prevent me from logging out:
<MyDisk>\<My Folder>\<Project Folder>\server\node_modules\passport\lib\sessionmanager.js:83
req.session.regenerate(function(err) {
^
TypeError: Cannot read properties of undefined (reading 'regenerate')
at Statement.<anonymous> (<MyDisk>\<My Folder>\<Project Folder>\server\node_modules\passport\lib\sessionmanager.js:83:17)
at Statement.<anonymous> <MyDisk>\<My Folder>\<Project Folder>\server\node_modules\connect-sqlite3\lib\connect-sqlite3.js:119:32)
It seems that session manager trying to regenerate the session which does not exist anymore and here's the error
I've implemented the solution showed in the next question req.session.destroy and passport logout aren't destroying cookie on client side but that doesn't work for me.
Pure req.logout doesn't work too.
Session does not destroy and req.user still has user after req.logout has been called
I figured it out.
Deleted req.destroy.session()
authRouter.post('/logout', async (req, res, next) => {
req.logout(req.user, (err)=> {
if (err) return next(err);
})
res.clearCookie('connect.sid');
res.send({isAuth: req.isAuthenticated(), user: req.user})})
And on the front-side:
await axios.post('/api/auth/logout',{}, {withCredentials: true})
.then(res => {
console.log('response ', res);
setUser(res.data.user)
})
.catch(e=> console.log(e))
Instead of:
await axios.post('/api/auth/logout',{withCredentials: true})
.then(res => {
console.log('response ', res);
setUser(res.data.user)
})
.catch(e=> console.log(e))
Related
I've got a react front end that performs some actions. The relevant axios requests look like so:
const login = async () => {
await Axios.post('http://localhost:8000/login', {
username: username,
password: password,
}).then((response) => {
console.log("login response: ", response);
window.location.href = "http://localhost:3000/";
}).catch(err => {
alert(err.response.data);
});
};
// run on first render to see if user session is still active - remove console log later
useEffect(() => {
Axios.get("http://localhost:8000/isLoggedIn").then((response) => {
console.log("isLoggedIn resonse: ", response);
if (response.data.loggedIn === true) {
setLoginStatus(`Logged in as ${response.data.user}`);
}
})
}, [])
const Logout = async () => {
try {
await Axios.get('http://localhost:8000/logout').then((response) => {
console.log(response);
window.location.href = "http://localhost:3000/login";
}).catch(err => {
alert(err);
});
} catch (error) {
alert(error)
}
};
I keep having to press log out twice to actually log my user out. The logout route runs before the "isLoggedIn" route, according to my network tab. And it's successful, too. Here are the isLoggedIn and logout routes in my express backend:
export function isLoggedIn( req: any, res: any) {
if (req.session.user) {
// if our session already has a user, send true to the frontend
// frontend runs this get login on first render, so will have user data if cookie has not expired.
res.send({loggedIn: true, user: req.session.user})
} else {
res.send({loggedIn: false});
}
}
export function logout(req: any, res: any) {
if (req.session) {
req.session.destroy( (err: any) => {
if (err) {
res.status(400).send('Unable to log out');
} else {
res.send("Logout successful");
}
});
} else {
res.end();
}
}
I'm getting a successful logout. I just cannot figure out why I need to hit the logout button twice on the frontend to actually destroy the session and log the user out? Is there something timing related that I may be missing here?
Seeking some help, trying to deploy a mern stack application on aws using ec2 instances. Everythings going well I can register new users and I can log in, problem is it doesn't stay logged in, the user instantly becomes undefined. Has anyone ran into this problem before? Think it might have something to do with the cookies. As Postman works good, its able to log in and then retrieve user.
Featured is the middleware code using Passport.
const LocalStrategy = passportLocal.Strategy;
mongoose.connect(...);
// Middleware
const app = express();
app.use(express.json());
//before deployment
//app.use(cors({origin:"http://localhost:3000",credentials:true}))
app.use(cookieParser());
app.use(cors({origin:"http://ec2-18-191-200-43.us-east-2.compute.amazonaws.com:3000",credentials:true}))
app.use(
session({
secret:"secretcode",
resave:true,
saveUninitialized:true,
cookie:{
sameSite:false
}
})
);
app.use(passport.initialize());
app.use(passport.session());
//Passport
passport.use(new LocalStrategy((username:string, password:string, done) => {
User.findOne({ username: username }, (err:any, user: DatabaseUserInterface) => {
if (err) throw err;
if (!user) {
console.log("not user");
return done(null, false);
}
bcrypt.compare(password, user.password, (err, result:boolean) => {
if (err) throw err;
if (result === true) {
console.log("good should be logged in");
return done(null, user);
} else {
//unuathorised
console.log("unathorised");
return done(null, false);
}
});
});
})
);
passport.serializeUser((user: DatabaseUserInterface, cb) => {
cb(null, user._id);
});
passport.deserializeUser((id: string, cb) => {
User.findOne({ _id: id }, (err: any, user: DatabaseUserInterface) => {
const userInformation :UserInterface = {
username: user.username,
isAdmin: user.isAdmin,
id: user._id,
wins:user.wins,
losses:user.losses
};
cb(err, userInformation);
});
});
//log in
app.post("/login", passport.authenticate("local"), (req, res) => {
res.send("success");
});
Follow up: Im an aws noob, and thought you had to run 2 different clusters with 2 different instances to run 2 tasks. You in fact only need 1 cluster with 1 instance that can run as many tasks as you want. This way cookies can actually be transferred between the ports used by the different tasks i.e. front and backend.
I have been trying to figure this out for weeks and either can't seem to understand the documentation, or something. I appreciate any help you can give.
I am using the Firebase SDK
I have my server-side route, in which I can access the token and could send it to the front:
const admin = require("firebase-admin")
admin.initializeApp()
exports.loginRoute = (req, res) => {
const user = {
email: req.body.email,
password: req.body.password
}
const { valid, errors } = validateLoginData(user)
if (!valid) {
return res.status(400).json(errors)
}
admin
.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then((data) => {
console.log(data.user.refreshToken, "refresh token")
return data.user.getIdToken(true)
})
.then((token) => {
return res.json({ token })
})
.catch((err) => {
console.error(err)
if (err.code === "auth/user-not-found") {
return res.status(400).json({ general: "User not found" })
} else if (err.code === "auth/wrong-password") {
return res
.status(400)
.json({ password: "User credentials don't match" })
} else {
res.status(500).json({
error: "Something went wrong, please try again."
})
}
})
}
Here is where I could use the refresh token (on the front end) to fetch a new authentication token, but I can't figure out how to create a route to do this:
if (token) {
const decodedToken = jwtDecode(token)
if (decodedToken.exp * 1000 < Date.now()) {
localStorage.setItem("Authentication", false)
//axios request to persist authentication would go here
}
}
Does anyone have a route that would work, or advice on what to do?
EDIT
const login = async (credentials) => {
let token
await axios
.post("/api/login", credentials)
.then((res) => {
token = res.data.token
const FBIdToken = `Bearer ${token}`
localStorage.setItem("token", token)
localStorage.setItem("FBIdToken", FBIdToken)
localStorage.setItem("Authentication", true)
context.setAuthenticated((prev) => true)
})
.then(() => {
context.getUserData()
})
.then(() => {
context.setUserState((prevUserState) => ({
...prevUserState,
token
}))
})
.catch((err) => {
context.setUserErrors((prev) => ({
...prev,
errors: err.response.data
}))
})
history.push("/")
}
Observer (client-side):
firebase.auth().onAuthStateChanged((user) => {
if (user) {
firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then((idToken) => {
const FBIdToken = `Bearer ${idToken}`
localStorage.setItem("FBIdToken", FBIdToken)
})
.catch((err) => {
console.log(err)
})
} else {
localStorage.removeItem("FBIdToken")
}
})
If you sign in with the Firebase Authentication JavaScript SDK in the client-side code, it already persists the user's sign-in state, and tries to restore it when you reload the page. You shouldn't have to do anything for that yourself.
It seems like you were using the same SDK in a server-side environment though, which is quite unusual. If you want to mint tokens yourself in a server-side environment, you should use the Firebase Admin SDK to do so. You can then send that token back to the client, and use it to sign in to Firebase Authentication there.
But for the vast majority of use-cases, I recommend using the Firebase Authentication SDK in your client-side code, so that the SDK managed refreshing of the token for you. If you then want to pass the token to the server, you can use getIdToken() as you do now. You can also monitor ID token generation, or more commonly monitor if a user's sign-in session is restored as shown in the first example of the documentation on detecting the current user.
I am having trouble with firebase and expo. When we do the signup process, and the user is generated through firebase, we sometimes get "undefined is not an object (evaluating 0.docs[0].data)" and the user is redirected to the entry point of the application instead of going to the next screen.
Most of the users will be able to go through the process without any problem. But few reported the same issues, and I have seen that with one account.
Here below the function that does not work properly
const createUser = async () => {
//Check if user already exists
await props.firebase.db
.collection('users')
.where('email', '==', props.userProfile.email)
.get()
.then(async (snapShot) => {
//if no document is found, save user to db
if (snapShot.docs.length === 0) {
await saveUserToDataBase()
.then(() => {
setShowLoader(false);
props.navigation.navigate('NotificationPermission');
})
.catch((err) => Alert.alert('An error occurred', err.message));
}
//else log an error, TODO: add error text in app.
else {
setShowLoader(false);
}
})
.catch((err) => {
Alert.alert('An error occurred', err.message);
});
};
const saveUserToDataBase = async () => {
//finds the correct collection and creates a new document within in containing the profile data.
await props.firebase.db
.collection('users')
.add({
first_name: props.userProfile.firstName,
email: props.userProfile.email.toLowerCase(),
phone_number: props.userProfile.phoneNumber,
organization: props.userProfile.organization,
profileId: props.userProfile.profileId,
})
.then(async (docRef) => {
await props.firebase.db
.collection('profile')
.doc(props.userProfile.profileId)
.update({
user_id: docRef.id,
})
.then(async () => {
await uploadProfilePhoto(docRef.id);
if (props.accessToken) {
props.navigation.navigate('NotificationPermission');
} else {
props.navigation.navigate('NotificationPermission');
}
})
.catch((err) => {
Alert.alert(
'An error occurred updating the users profile ',
err.message,
);
});
})
.catch((err) => {
Alert.alert('An error occurred creating the user', err.message);
});
};
I have used the Alert component to check the error directly, the error is consistent with few email addresses. I have upgraded the firebase package, did no do anything.
I feel like the onAuthStateChanged could be responsible of that but I am not sure how to handle that? Your help or suggestions will be greatly appreciated.
I am working on a MERN app and using firebase authentication at the client side.
When a user is logging in for the first time, then that user's info(from firebase) will be saved in the database.
export const startLogIn = () =>{
return firebase.auth().signInWithRedirect(googleAuthProvider)
.then(result => {
if(result.additionalUserInfo.isNewUser){
console.log(result.user);
axios.post(`http://localhost:5000/api/user`, result.user)
.then(() => console.log(newUser.data))
.catch(err => console.log(err))
}else{
console.log('existing user');
}
});
};
And in app.js I am fething the user data from database
firebase.auth().onAuthStateChanged((user) => {
if(user){
store
.dispatch(usersFetchData(`http://localhost:5000/api/user/${user.uid}`));
store.dispatch(startSetPost()).then(() => {
renderApp();
if(history.location.pathname === '/'){
history.push('/dashboard');
}
});
}
else{
console.log('log out');
store.dispatch(logout());
renderApp();
history.push('/');
}
});
But the the strategy is not working as axios request is async, so before
completion of the post request, the app.js dispacthing function starts running.