Firebase auth onAuthStateChanged resolving to null - javascript

I'm trying to get the current user UID from a Firebase database - a simple task in principle. I'm following the guide for managing users from the firebase documentation but I still am unable to get a solution. Everywhere I see that firebase.auth().currentuser will not be defined during initialisation - ok great. I use a listener instead, and the listener fires as it should. But for some reason when it fires, the value of user is null.
export default class ShoppingCategories extends Component {
constructor(props) {
super(props);
this.state = {
GeoFire: new GeoFire(firebase.database().ref('stores-geo').push()),
stores: exstores,
brands: [],
storesSelected: []
}
}
componentDidMount() {
this.getStoreTile();
firebase.auth().onAuthStateChanged((user) =>{
if(user){
this.locateStores(user);
}else{
console.log("No user found: "+user);
}
});
}
...
Not sure what I'm doing wrong here. I get
No user found: null
printed to the console.

Solved my own problem. My service worker was causing the page to be served from cache. I removed the service worker caching feature which fixed the problem. Not exactly sure how to fix this long term right now but will update once I solve.

Related

login an anonymous user forever in Firebase with React Native

I'm doing a scheduler (calendar) app using React Native. You can add tasks to whatever day you want to do them. To store those tasks I'm using Firebase.
Now, I want that the tasks shown in each device are different. I though the best way to achieve this is logging the user anonymously (so that user doesn't have to sign up, which would be nonsense, in my opinion), and by doing so, only tasks owned by that user can show.
I have achieved signing up a new anonymous user (and I can see the new user in the project in Firebase web page). However, I'm struggling to make that logging last forever.
I had written this piece of code in the first function that my app renders:
useEffect(() => {
auth.onAuthStateChanged(function (user) {
if (user) {
navigation.navigate("Home");
} else {
auth.signInAnonymously().then(navigation.navigate("Home"));
}
});
});
where auth = firebase.auth(). However, each time that I reload my app, a new user is created.
Can anyone help me? Thanks in advance! :)
Have you tried to set RN persistence ?
import {
initializeAuth,
getReactNativePersistence,
} from 'firebase/auth/react-native'
import AsyncStorage from '#react-native-async-storage/async-storage'
export const auth = initializeAuth(app, {
persistence: getReactNativePersistence(AsyncStorage),
})

How to ensure user is logged in on refresh in firebase? [duplicate]

When mounting a component, I'd like to render either a log in screen or the app, depending on wether the user in logged in. However, each time I refresh, the user is logged out. How would I keep them logged in?
App Component:
firebase.initializeApp(config); //init the firebase app...
class App extends Component {//this component renders when the page loads
constructor(props){
super(props);
this.state = {user: firebase.auth().currentUser};//current user
}
render(){
if (this.state.user) {//if you are logged in
return (
<Application/>
);
} else {//if you are not logged in
return (
<Authentication/>
);
}
}
}
This is the method I'm using to log in the user (which works fine):
let email = "some";
let password = "thing";
const auth = firebase.auth();
const promise = auth.signInWithEmailAndPassword(email, password);
The user's token is automatically persisted to local storage, and is read when the page is loaded. This means that the user should automatically be authenticated again when you reload the page.
The most likely problem is that your code doesn't detect this authentication, since your App constructor runs before Firebase has reloaded and validated the user credentials. To fix this, you'll want to listen for the (asynchronous) onAuthStateChanged() event, instead of getting the value synchronously.
constructor(props){
super(props);
firebase.auth().onAuthStateChanged(function(user) {
this.setState({ user: user });
});
}
I had the same issue with Firebase in React. Even though Firebase has an internal persistence mechanisms, you may experience a flicker/glitch when reloading the browser page, because the onAuthStateChanged listener where you receive the authenticated user takes a couple of seconds. That's why I use the local storage to set/reset it in the onAuthStateChanged listener. Something like the following:
firebase.auth.onAuthStateChanged(authUser => {
authUser
? localStorage.setItem('authUser', JSON.stringify(authUser))
: localStorage.removeItem('authUser')
});
Then it can be retrieved in the constructor of a React component when the application starts:
constructor(props) {
super(props);
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
You can read more about it over here.
Personally, I feel this way is the best and the most simple.
state = {authUser: null,show:'none'};
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) { this.setState({ authUser: true,show:'block'})}
else { this.setState({ authUser: false,show:'none'})}
})
}
return{ /*important*/ this.state.authuser==false?
<Login/>
:this.state.authuser==true?
<Home style={{display:this.state.show}}/>
:<div>/*show loading/spash page*/
</div>
}
You can also add routing to this with Home as the default route ('/' do not use exact path use path instead) and then make it auto reroute to the login or signup if the user is not logged in.

How do I integrate the cognito hosted UI into a react app?

I am creating a react app - using create-react-app and amplify - and I am trying to set up authentication. I don't seem to be able to handle the federated logins using the hosted UI.
There are some pages which require no authentication to reach and then some which require a user to be logged in. I would like to use the hosted UI since that's prebuilt. I have been following the getting started docs here: https://aws-amplify.github.io/docs/js/authentication
For background I have the following components:
- Amplify - an amplify client which wraps calls in methods like doSignIn doSignOut etc. The idea is to keep all this code in one place. This is a plain javascript class
- Session - provides an authentication context as a React context. This context is set using the amplify client. It has HOC's for using the context
- Pages - some wrapped in the session HOC withAuthentication which only renders the page if the user has logged in
This structure is actually taken from a Firebase tutorial: https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial/
Maybe this is just not feasible with Amplify? Though the seem similar enough to me that it should work. The basic idea is that the Session provides a single auth context which can be subscribed to by using the withAuthentication HOC. That way any component that requires a user will be rendered as soon as a user has logged in.
Originally I wrapped the entire App component in the withAuthenticator HOC provided by amplify as described in the docs. However this means that no pages are accessible without being authenticated - home page needs to be accessible without an account.
Next I tried calling to the hosted UI with a sign in button and then handling the response. The problem is when the hosted UI has logged a user in then it redirects back to the app causing it to reload - which is not ideal for a single page app.
Then I tried checking if the user is authenticated every time the app starts - to deal with the redirect - but this becomes messy as I need to move a lot of the amplify client code to the Session context so that it can initialise correctly. The only way I can see to get this is using the Hub module: https://aws-amplify.github.io/docs/js/hub#listening-authentication-events The downside is that after logging in, the app refreshes and there's still a moment when you are logged out which makes the user experience weird.
I would have thought that there would be a way to not cause an application refresh. Maybe that's just not possible with the hosted UI. The confusing thing to me is that the documentation doesn't mention it anywhere. In actual fact there is documentation around handling the callback from the hosted UI which as far as I can see never happens because the entire page refreshes and so the callback can never run.
I've tried to trim this down to just what's needed. I can provide more on request.
Amplify:
import Amplify, { Auth } from 'aws-amplify';
import awsconfig from '../../aws-exports';
import { AuthUserContext } from '../Session';
class AmplifyClient {
constructor() {
Amplify.configure(awsconfig);
this.authUserChangeListeners = [];
}
authUserChangeHandler(listener) {
this.authUserChangeListeners.push(listener);
}
doSignIn() {
Auth.federatedSignIn()
.then(user => {
this.authUserChangeListeners.forEach(listener => listener(user))
})
}
doSignOut() {
Auth.signOut()
.then(() => {
this.authUserChangeListeners.forEach(listener => listener(null))
});
}
}
const withAmplify = Component => props => (
<AmplifyContext.Consumer>
{amplifyClient => <Component {...props} amplifyClient={amplifyClient} />}
</AmplifyContext.Consumer>
);
Session:
const provideAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: null,
};
}
componentDidMount() {
this.props.amplifyClient.authUserChangeHandler((user) => {
this.setState({authUser: user});
});
}
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withAmplify(WithAuthentication);
};
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
render() {
return (
<AuthUserContext.Consumer>
{user =>
!!user ? <Component {...this.props} /> : <h2>You must log in</h2>
}
</AuthUserContext.Consumer>
);
}
}
return withAmplify(WithAuthentication);
};
The auth context is provided once at the top level:
export default provideAuthentication(App);
Then pages that require authentication can consume it:
export default withAuthentication(MyPage);
What I would like to happen is that after the user signs in then I can set the AuthUserContext which in turn updates all the listeners. But due to the redirect causing the whole app to refresh the promise from Auth.federatedSignIn() can't resolve. This causes the user to be displayed with You must log in even though they just did.
Is there a way to block this redirect whilst still using the hosted UI? Maybe launch it in another tab or in a popup which doesn't close my app? Or am I going about this the wrong way? It just doesn't feel very 'Reacty' to cause full page refreshes.
Any help will be greatly appreciated. I can provide more details on request.
Instead of chaining onto the Auth's promise, you can use Amplify's build-in messaging system to listen to events. Here is how I do it in a custom hook and how I handle what gets rendered in Redux.
import { Auth, Hub } from 'aws-amplify';
import { useEffect } from 'react';
function useAuth({ setUser, clearUser, fetchQuestions, stopLoading }) {
useEffect(() => {
Hub.listen('auth', ({ payload: { event, data } }) => {
if (event === 'signIn') {
setUser(data);
fetchQuestions();
stopLoading();
}
if (event === 'signOut') {
clearUser();
stopLoading();
}
});
checkUser({ fetchQuestions, setUser, stopLoading });
}, [clearUser, fetchQuestions, setUser, stopLoading]);
}
async function checkUser({ fetchQuestions, setUser, stopLoading }) {
try {
const user = await Auth.currentAuthenticatedUser();
setUser(user);
fetchQuestions();
} catch (error) {
console.log(error);
} finally {
stopLoading();
}
}

How do I catch changes in state from Firebase Auth?

I am using Angular 2 with angularfire library. I am having trouble catching changes in the user's state.
First) Logging in works fine. I see the user come through to my subscribe function and all is dandy.
However, When I disable that user on the Firebase console...The user state has changed. Why am I not seeing that change in state come through in my app component?
I would like to captures errors of different types, and handle them according to the error code. To do this, I created a service here:
#Injectable()
export class CZAuthService {
constructor(private af: AngularFire){ }
getAuthState() : Observable<FirebaseAuthState>{
return this.af.auth.catch((error)=>{
console.log(error);
return this.af.auth;
})
}
}
I would like to catch exceptions here in this catch operator, and then return the stream to maintain connection to the stream.
Then in my root app component, I am subscribing to this stream and handling when the user is signed in:
this.auth.getAuthState().subscribe((user)=>{
console.log(user);
if(user != null){
this._store.dispatch(new UserLogInSuccessAction(user))
}
});

React & Firebase + Flux : Child component does not render

I've already checked this SO article, however, the solution does not work. I have a simple messaging app using Firebase + Flux:
App
-UserList Component (sidebar)
-MessageList Component
The UserList component gets props from this.state.threads (state belongs to main App component).
In my Flux ThreadStore, I have the following event listeners bound to a firebaseRef:
const _firebaseThreadAdded = (snapshot) => {
threadsRef.child(snapshot.key()).on('value', (snap) => {
console.log('thread added to firebase');
_threads.push({
key: snap.key(),
threadType: snap.val().threadType,
data: snap.val()});
});
}
ref.child(ref.getAuth().uid).child('threads').on('child_added', Actions.firebaseThreadAdded);
The UserList uses this.props.threads.map(thread => { return(<li>{thread.name}</li>) } ) (summarised) to then render a list of thread names in the side bar based on the thread keys in the firebaseRef/userId/threads.
However, this does not render any list of users until I click any button in the app or delete a reference directly from the Firebase Forge UI (I also have a 'child_removed' event listener).
For the life of me I cannot figure this out. Can anyone explain why this is happening / how to fix it? I've spent a whole half day trying and haven't come up with anything.
FYI:
Relevant Dispatcher.register entry:
case actionConstants.FIREBASE_THREAD_ADDED:
_firebaseThreadAdded(action.data);
threadStore.emitChange();
break;
Relevant Actions entry:
firebaseThreadAdded(data) {
AppDispatcher.handleAction({
actionType: actions.FIREBASE_THREAD_ADDED, data
});
},
Okay, so I figured it out after a lot of reading - a little new to Flux. The issue was here:
case actionConstants.FIREBASE_THREAD_ADDED:
_firebaseThreadAdded(action.data);
threadStore.emitChange();
break;
The emitChange() action was being triggered before the Firebase promise was being resolved, and hence the child components were updating hte state without any data. Hence, the list was not being rendered. What I did to change this was re-architect the function so that threadStore.emitChange() would only be triggered on resolve:
const _firebaseThreadAdded = (snapshot) => {
threadsRef.child(snapshot.key()).once('value').then(snap => {
console.log('thread added to firebase: ', snap.key());
_threads.push({
threadId: snap.key(),
threadType: snap.val().threadType,
data: snap.val()});
threadStore.emitChange();
});
}
and the resulting dispatcher register entry:
case actionConstants.FIREBASE_THREAD_ADDED:
_firebaseThreadAdded(action.data);
break;
Don't know if this is the best way of handling this, but it's doing the job - the emit event is only triggered once firebase has loaded. Hope this helps anyone with a similar issue for promises/firebase/ajax!

Categories

Resources