I'm currently working on authentication of an application in the frontend. I don't have access to the backend.
User state is managed via redux and the authentication requires a token.
While testing I notice that when I'm logged in and via Postman (for example) I authenticate the user the app return an unidentified state. I am still able to navigate the private routes but I can not see the data I fetch from the backend.
Here is how I am managing the logged / !logged state for the private route.
I wonder if I am getting something wrong and if not, if its possible to overcome this issue of potentially "stealed" token... Imagine 2 person working using the same username/password... (wrong, but technically possible)
import { useState, useEffect } from 'react'
import store from '../store'
export const useAuthStatus = () => {
const [loggedIn, setLoggedIn] = useState(false)
const [checkingStatus, setCheckingStatus] = useState(true)
const user = store.getState().userLogin
const status = user.userDetails.status
useEffect(() => {
if (status === "ok") {
setLoggedIn(true)
} else {
setLoggedIn(false)
}
setCheckingStatus(false)
}, [status, user])
return { loggedIn, checkingStatus }
}
Related
Solved
Although not exactly my solution to this problem, the answer given below from #Po Wen Chen was helpful but it doesn't work exactly as I want it to. data in the form of proxy continues to come, this is not important, the conditions are met.
The main problem was that every time the page was refreshed, the states were null, so their values were flying. After we searched, my states became permanent with the library named pinia-plugin-persistedstate.
Solved
I did as it is written in the documentation, but instead of receiving a user data, I am getting a proxy type data.
Documentation of pinia describing how to use store outside of component
The account store performs the registration and login of the users, that is, the auth processes.
import axios from "axios";
import { defineStore } from "pinia";
const useAccountStore = defineStore("account", {
state: () => ({
user: null,
returnUrl: null
}),
getters: {
isLoggedIn: state => (state.user ? true : false),
getUser: state => state.user,
},
actions: {
async init() {
console.log('run the init')
this.fetchUser()
},
async registerUser(user) {
await axios.post("/account/register", {user})
},
async login(credentials) {
const user = await axios.post("/account/session", credentials)
this.user = user.data
},
async logout() {
await axios.delete("/account/session")
this.user = null
},
async fetchUser() {
const user = await axios.get("/account")
this.user = user.data
},
},
})
export { useAccountStore };
In main.js
import antd from "ant-design-vue"
import "ant-design-vue/dist/antd.css"
import axios from "axios"
import { createPinia } from "pinia"
import { createApp } from "vue"
import App from "./app.vue"
import { router } from "./router"
import { useAccountStore } from "./store/account.store"
// import './assets/main.css'
axios.defaults.baseURL = import.meta.env.VITE_API_URL
axios.defaults.withCredentials = true
createApp(App)
.use(createPinia())
.use(router)
.use(antd)
.mount("#app")
useAccountStore().init()
router.beforeEach(async (to) => {
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login', '/register'];
const authRequired = !publicPages.includes(to.path);
const authStore = useAccountStore();
console.log('authStore.isLoggedIn', authStore)
if (authRequired && !authStore.user) {
authStore.returnUrl = to.fullPath;
return '/login';
}
});
I press the authStore to console and it comes back to me as a proxy.
return the proxy
Although not exactly my solution to this problem, the answer given below from #Po Wen Chen was helpful but it doesn't work exactly as I want it to. data in the form of proxy continues to come, this is not important, the conditions are met.
The main problem was that every time the page was refreshed, the states were null, so their values were flying. After we searched, my states became permanent with the library named pinia-plugin-persistedstate.
This seems like an easy one and that I'm just missing something obvious, but a little background:
I am making a mock "bug reporting" web app. The app has two types of user: "user" and "engineer". Upon signup the user is assigned a type, and it's saved in a firebase collection "users" under a firebase unique identifier "uid".
Upon login to the web app, a firebase user object for the logged in user is retrieved from firebase. This object has "user.uid" on it.
In order to grab the user type (saved as userType in the firebase document) I need to take that user.uid and send the uid part to a react hook I have made. This hook then fetches the document with that uid and returns the userType.
I've made the following dummy file to demonstrate.
import {React, useEffect, useState} from 'react'
import { useAuthContext } from '../../hooks/useAuthContext'
import { useDocument } from '../../hooks/useDocument'
export default function User() {
const { user } = useAuthContext();
console.log("uid: " + user.uid)
const id = user.uid;
console.log("id: " + id)
let { document, error } = useDocument("users", id)
console.log("userType: " + document.userType)
return (
<div>
</div>
)
}
Now the problem I have is that "user" isn't initalised from Context in time before the program tries to go fetch it, using a uid that, again, isn't initialised in time.
Basically I just need a way to delay using the useDocument hook, but I can't make hooks asynchronous. My async skills aren't my strongest point, to say the least.
Any help is massively appreciated, I've spent so many hours trying to crack this.
EDIT: Upon request, here is the useDocument hook:
import { useEffect, useState } from "react"
import { projectFirestore } from "../firebase/config"
export const useDocument = (collection, id) => {
const [document, setDocument] = useState(null)
const [error, setError] = useState(null)
// realtime document data
useEffect(() => {
const ref = projectFirestore.collection(collection).doc(id)
const unsubscribe = ref.onSnapshot(snapshot => {
// need to make sure the doc exists & has data
if(snapshot.data()) {
setDocument({...snapshot.data(), id: snapshot.id})
setError(null)
}
else {
setError('No such document exists')
}
}, err => {
console.log(err.message)
setError('failed to get document')
})
// unsubscribe on unmount
return () => unsubscribe()
}, [collection, id])
return { document, error }
}
I'm uploading a user profile image into firebase storage, then updating the photoURL value of the auth user with updateProfile(). After that I want the user to be updated without manually refreshing the page. I've been trying this for days now and the issue gets more weird every time I try to debug it.
The interesting thing is the user object seems to be already updated when I log it with console.log(currentUser) after the then promise of updateProfile() is fulfilled. So the new photoURL is already present in the currentUser object. But it seems to not call a state update or console.log("!!!!currentAuthUserUpdate", user);. So the user image wouldn't refresh in my page.
I even tried it with doing a useEffect with the currentUser as a dependency but it wasn't fired. Still, the currentUser object changed when logging it after updateProfile()
Updating the profile, UpdateUserImage.tsx:
import { useAuth } from "../../contexts/AuthContext";
const { currentUser } = useAuth();
// updating the user profile
updateProfile(currentUser, { photoURL })
AuthContext.tsx:
import { auth } from "./../firebase/firebase";
const [currentUser, setCurrentUser] = useState(null);
const auth = getAuth(app);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
console.log("!!!!currentAuthUserUpdate", user);
// I tried setting the user as a custom new object: const userData = { ...user };
setCurrentUser(user);
});
return unsubscribe;
}, []);
firebase.js
import { getAuth } from "firebase/auth";
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
export {
auth
};
What I tried additionally: (But this wouldn't work as the user is already updated without a state refresh from react, so it's trying to replace the same object)
const reloadUser = async () => {
try {
const res = await currentUser.reload();
const user = auth.currentUser;
console.log("currentUser:", user);
setCurrentUser(auth.currentUser);
console.log("res", res);
} catch (err) {
console.log(err);
}
};
it's not auth.onAuthStateChanged. You need to import onAuthStateChanged from 'firebase/auth'
import { getAuth, onAuthStateChanged } from "firebase/auth"
const auth = getAuth(); // leave getAuth empty if you only have one app
Then in your useEffect it should be
onAuthStateChanged(auth, async (currentUser) => { ... }
The setCurrentUser function returned from useState isn't always the same function in my experience.
You can try passing it as a dependency into the useEffect - but I don't think that's what you want.
React lets you use an old useState setter if you give it an updater function, rather than a value: setCurrentUser(()=>auth.currentUser)
The React docs dispute this though.
Using useStates are good for re-rendering components. However going into utilizing useRefs are best for updating the actual variable and will not cause a re-render of the component.
Declare it like:const currentUser = useRef(null)
Update it like: currentUser.current = updatedUserData
Use in code like: currentUser.current.photoURL
I've searched on here already but it seems all the answers are very outdated or they are questions that haven't been answered.
I've got an expo app SDK 43 and I'm using their auth library to authorize a reddit login. I've followed the example here https://docs.expo.dev/guides/authentication/#reddit to produce this code
import React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native-paper';
import { useAppDispatch } from '../../common/hooks/redux';
import { setAuthCode } from '../../common/state/authSlice';
WebBrowser.maybeCompleteAuthSession();
// Endpoint
const discovery = {
authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize.compact',
tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
};
const LoginScreen = () => {
const dispatch = useAppDispatch();
const [request, response, promptAsync] = useAuthRequest(
{
responseType: ResponseType.Token,
clientId: 'MY_CLIENT_ID',
scopes: ['identity'],
redirectUri: makeRedirectUri({
scheme: undefined,
}),
},
discovery
);
React.useEffect(() => {
console.log(`response is ${response}`);
if (response?.type === 'success') {
const { access_token } = response.params;
dispatch(setAuthCode(access_token));
console.log(access_token);
} else {
console.log(response);
}
}, [response]);
return (
<Button
disabled={!request}
onPress={() => {
promptAsync();
}}
>
Login
</Button>
);
};
export default LoginScreen;
But despite the fact that the login button correctly takes me to the login screen, I successfully log in and allow the app (and if I go onto the web separately I can see in my account that the app is there under the authorized apps.)
At this point on my device one of two things happens: 1. something causes the app to disconnect from metro and it hangs on a loading wheel belonging to the greater expo stuff, or 2. It successfully gets back to the app but it redownloads the bundle and the response is null.
What is screwing up here?
I am trying to use Firebase Authentication on my Next.js website. For some reason, when I press the sign up button, nothing happens and no errors are logged. It just refreshes the page. It doesn't even set any cookies or create a user.
Here are my two files related to authentication:
utils/authProvider.js:
import firebase from 'firebase/app';
import 'firebase/auth';
if(!firebase.apps.length) {
firebase.initializeApp({
// config
});
}
const auth = firebase.auth();
module.exports = { auth };
pages/signup.js:
import { useState } from 'react'
import { LockClosedIcon } from '#heroicons/react/solid'
import { auth } from '../utils/authProvider'
export default function CreateAccount() {
const [emailField, setEmailField] = useState('');
const [passwordField, setPasswordField] = useState('');
// emailField and passwordField are set correctly, I used console.log to test it
const createAccount = () => {
// this event does get triggered, I used console.log to test it
auth.createUserWithEmailAndPassword(emailField, passwordField)
.then((userCredential) => {
window.location.replace('/');
console.log('logged in!');
}).catch((error) => {
console.error(error);
});
}
Edit: After experimenting some more, I saw that the request to https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser never gets completed. It just shows as red in the network traffic tab of Chrome dev tools. Any reason why this might happen?
Thanks in advance.