Firebase Auth, Logged out on page refresh - reactjs

Technologies: I'm using Firebase Auth with NextJS & React.
Problematic: Logged users can use the web app with firebase auth normally only if they navigate within the app via Next routing, whenever they refresh the page or open a new account tab they are not logged in anymore.
Issue: It is extremely frustrating because this problem only occurs on production. There's no problem at all on the staging & localhost environment.
firebase.js: Initialize firebase.
import getConfig from "next/config";
import * as firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/analytics';
const { publicRuntimeConfig } = getConfig();
export async function initializeFirebase() {
if (!firebase.apps.length) {
firebase.initializeApp(JSON.parse(publicRuntimeConfig.FIREBASE_CONFIG));
if (publicRuntimeConfig.FIREBASE_ANALYTICS) {
firebase.analytics();
}
}
}
export const auth = firebase.auth
export const db = firebase.firestore;
export default firebase;
AuthHoC.js: To make sure the user is connected I wrapper my pages with a HOC.
export default App => (
class AuthHoC extends App {
_isMounted = false;
constructor(props) {
super(props)
this.state = {
loading: false,
isVerified: false,
idToken: undefined,
isAuthenticated: false
}
}
async componentDidMount() {
this._isMounted = true;
await initializeFirebase();
// onAuthStateChanged return a function that we'll use to unsubscribe our listener
this.unsubscribeMethod = await auth().onAuthStateChanged(this._handleStateChange);
}
// is user is null, we're no longer authenticated
_handleStateChange = (user) => {
let that = this;
if (user) {
// NOT PASSING HERE ON PAGE REFRESH...
user.getIdToken().then(function(idToken) {
that.setState({
loading: true,
idToken: idToken,
isVerified: user.emailVerified,
isAuthenticated: !!user
});
});
} else {
...
}
}
componentWillUnmount() {
if (this.unsubscribeMethod) {
this.unsubscribeMethod();
}
this._isMounted = false;
}
render() {
return ( <>
{this.state.loading ?
<App {...this.props} {...this.state} />
:
... loading ...
}
</> )
}
});
_app.js: (NextJS) Wrap every pages with the Higher Order Component.
import App from "next/app";
import AuthHoC from '../utils/authentication/authHoC';
class MyApp extends App {
render() {
const { Component, pageProps, isAuthenticated, idToken, isVerified } = this.props;
return (
<Component
{...pageProps}
isAuth={isAuthenticated}
idToken={idToken}
isVerified={isVerified}
/>
);
}
}
export default AuthHoC(MyApp);
What could be the issue? All these codes work on localhost & staging url, just not on production.
EDIT:
I pinpointed the problem, I just switched my production keys with staging and it works, that means that the problem is not coming from the Heroku or my code but my Firebase configuration itself.
The main difference is that the prod use analytics. If you have any info I forgot to configure any suggestion would help.

Related

keycloak Single-sign-on not working with react application

If the keycloak user used by my react application is already signed in via another application then it shouldn't ask for authentication again via my react application (since SSO).how can i achieve this ??please provide solution to it.
i am using keycloak to authenticate a react application.keycloak authentication is working fine.tried with single-sign-on but its not working.i want that react application to work with keycloak single-sign-on i.e when keycloak user is loginned it should not ask again for login credentials.react application should work with single-sign-on.how can i achieve this ??please provide solution to it.
below is my keycloak.json
{
"realm": "Google-Auth",
"auth-server-url": "http://localhost:8180/auth",
"ssl-required": "external",
"resource": "googledemo",
"public-client": true,
"confidential-port": 0
}
below is secured.js
`import React, { Component } from 'react';
import UserInfo from './UserInfo';
import Logout from './Logout';
import Keycloak from 'keycloak-js';
class Secured extends Component {
constructor(props) {
super(props);
this.state = { keycloak: null, authenticated: false };
}
componentDidMount() {
const keycloak = Keycloak('/keycloak.json');
keycloak.init({onLoad: 'login-required'}).then(authenticated => {
this.setState({ keycloak: keycloak, authenticated: authenticated })
})
}
render() {
if(this.state.keycloak) {
if(this.state.authenticated) return (
<div>
<p>
This is a Keycloak-secured component of your application. You shouldn't be able to
unless you've authenticated with Keycloak.
</p>
<UserInfo keycloak={this.state.keycloak} />
<Logout keycloak={this.state.keycloak} />
</div>
); else return (<div>Unable to authenticate!</div>)
}
return (
<div>Initializing Keycloak...</div>
);
}
}
export default Secured;
I have come across a similar scenario. I used the same keycloak-js package as well. Instead of using that in component, you can try to create a service from it
import Keycloak from "keycloak-js";
const _kc = new Keycloak('/keycloak.json');
/**
* Initializes Keycloak instance and calls the provided callback function if successfully authenticated.
*
* #param onAuthenticatedCallback
*/
const initKeycloak = (onAuthenticatedCallback) => {
_kc.init({
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
pkceMethod: 'S256',
})
.then((authenticated) => {
if (authenticated) {
onAuthenticatedCallback();
} else {
doLogin();
}
})
};
const doLogin = _kc.login;
const doLogout = _kc.logout;
const getToken = () => _kc.token;
const isLoggedIn = () => !!_kc.token;
const updateToken = (successCallback) =>
_kc.updateToken(5)
.then(successCallback)
.catch(doLogin);
const getUsername = () => _kc.tokenParsed?.preferred_username;
const hasRole = (roles) => roles.some((role) => _kc.hasRealmRole(role));
const UserService = {
initKeycloak,
doLogin,
doLogout,
isLoggedIn,
getToken,
updateToken,
getUsername,
hasRole,
};
export default UserService;
And then use the index to initiate this keycloak service and pass the render of your application as a call back.
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import HttpService from "./services/HttpService";
import UserService from "./services/UserService";
const renderApp = () => ReactDOM.render(<App/>, document.getElementById("app"));
UserService.initKeycloak(renderApp);
HttpService.configure();

Can I use a react HOC in this way without future pitfalls

I learn ReactJs and have a design Composition question about ReactJs higher order component (HOC).
In the code below App.jsx I use this withAuthentication HOC that initializes app core processes. This HOC value is not used in the App.js. Therefore I must suppress all withAuthentication HOC render callbaks and I do that in the shouldComponentUpdate by returning false.
(I use this HOC in many other places to the get HOC's value but not in App.jsx)
File App.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { getAlbumData } from './redux/albumData/albumData.actions';
import { getMetaData } from './redux/albumMetaData/albumMetaData.actions';
import Header from './components/structure/Header';
import Content from './components/structure/Content';
import Footer from './components/structure/Footer';
import { withAuthentication } from './session';
import './styles/index.css';
class App extends Component {
componentDidMount() {
const { getMeta, getAlbum } = this.props;
getMeta();
getAlbum();
}
shouldComponentUpdate() {
// suppress render for now boilerplate, since withAuthentication
// wrapper is only used for initialization. App don't need the value
return false;
}
render() {
return (
<div>
<Header />
<Content />
<Footer />
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
getMeta: () => dispatch(getMetaData()),
getAlbum: () => dispatch(getAlbumData()),
});
export default compose(connect(null, mapDispatchToProps), withAuthentication)(App);
The HOC rwapper WithAuthentication below is a standard HOC that render Component(App) when changes are made to Firebase user Document, like user-role changes, user auth-state changes..
File WithAuthentication .jsx
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import AuthUserContext from './context';
import { withFirebase } from '../firebase';
import * as ROLES from '../constants/roles';
import { setCurrentUser, startUserListener } from '../redux/userData/user.actions';
import { selectUserSlice } from '../redux/userData/user.selectors';
const WithAuthentication = Component => {
class withAuthentication extends React.Component {
constructor() {
super();
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
componentDidMount() {
const { firebase, setUser, startUserListen } = this.props;
this.authListener = firebase.onAuthUserListener(
authUser => {
this.setState({ authUser });
setUser(authUser);
startUserListen();
},
() => {
localStorage.removeItem('authUser');
this.setState({ authUser: null });
const roles = [];
roles.push(ROLES.ANON);
firebase
.doSignInAnonymously()
.then(authUser => {
if (process.env.NODE_ENV !== 'production')
console.log(`Sucessfully signed in to Firebase Anonymously with UID: ${firebase.getCurrentUserUid()}`);
firebase.doLogEvent('login', { method: 'Anonymous' });
firebase
.userDoc(authUser.user.uid)
.set({
displayName: `User-${authUser.user.uid.substring(0, 6)}`,
roles,
date: firebase.fieldValue.serverTimestamp(),
})
.then(() => {
console.log('New user saved to Firestore!');
})
.catch(error => {
console.log(`Could not save user to Firestore! ${error.code}`);
});
})
.catch(error => {
console.error(`Failed to sign in to Firebase: ${error.code} - ${error.message}`);
});
},
);
}
componentWillUnmount() {
this.authListener();
}
render() {
const { currentUser } = this.props;
let { authUser } = this.state;
// ALl changes to user object will trigger an update
if (currentUser) authUser = currentUser;
return (
<AuthUserContext.Provider value={authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
withAuthentication.whyDidYouRender = true;
const mapDispatchToProps = dispatch => ({
setUser: authUser => dispatch(setCurrentUser(authUser)),
startUserListen: () => dispatch(startUserListener()),
});
const mapStateToProps = state => {
return {
currentUser: selectUserSlice(state),
};
};
return compose(connect(mapStateToProps, mapDispatchToProps), withFirebase)(withAuthentication);
};
export default WithAuthentication;
My question is will this hit me later with problems or is this ok to do it like this?
I know a HOC is not suppose to be used like this. The WithAuthentication is taking care of Authentication against Firebase and then render on all user object changes both local and from Firestore listener snapshot.
This HOC is used in many other places correctly but App.jsx only need to initialize the HOC and never use it's service.
My question is will this hit me later with problems or is this ok to do it like this?

Authentication in React Native with AsyncStorage

In my project, when uid is saved in AsyncStorage, it means the user has already logged in.
Current code
Index.js
import React from 'react';
import Auth from 'app/src/common/Auth';
import { Text } from 'react-native';
export default class Index extends React.Component {
constructor(props) {
super(props);
this.state = {
auth: true,
};
}
async componentDidMount() {
await this.setState({ auth: await Auth.user() });
}
render() {
const { auth } = this.state;
return (
{!auth ? (
<Text>You need to Log In!</Text>
) : (
<Text>You are Logged In!</Text>
)}
)
}
}
Auth.js
import { AsyncStorage } from 'react-native';
export default {
async user() {
let result = true;
const response = await AsyncStorage.getItem('uid');
if (!response) {
result = false;
}
return result;
},
};
This code is working but I would like to make this more simple like one function.
I would appreciate it if you could give me any advice.
You can use promise and Do all Job in Index.js like
AsyncStorage.getItem('uid').then((uid) => {
this.setState({
auth : (uid) ? true : false
})
})
or simply use
const uid = await AsyncStorage.getItem('uid');
this.setState({ auth : (uid) ? true : false });
Are you going to be using that in more than one spot? why not just do it in your component?
async componentDidMount() {
const auth = await AsyncStorage.getItem('uid');
this.setState({ auth });
}

Amplify+react: hosted ui with federated identity provider

I would like to use the hosted ui with amplify in react.
The authentication should be done using federated identity provider, which is working correctly.
But I don't know, how to write the react part.
I found in amplify tutorial: that is should be possible with this sample:
// OAuthButton.js
import { withOAuth } from 'aws-amplify-react';
import React, { Component } from 'react';
class OAuthButton extends Component {
render() {
return (
<button onClick={this.props.OAuthSignIn}>
Sign in with provider's account
</button>
)
}
}
export default withOAuth(OAuthButton);
// App.js
import React, { Component } from 'react';
import './App.css';
import OAuthButton from './OAuthButton';
import Amplify, { Auth, Hub } from 'aws-amplify';
import awsconfig from './aws-exports'; // your Amplify configuration
// your Cognito Hosted UI configuration
const oauth = {
domain: 'your_cognito_domain',
scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
redirectSignIn: 'http://localhost:3000/',
redirectSignOut: 'http://localhost:3000/',
responseType: 'code' // or 'token', note that REFRESH token will only be generated when the responseType is code
};
Amplify.configure(awsconfig);
Auth.configure({ oauth });
class App extends Component {
constructor(props) {
super(props);
this.signOut = this.signOut.bind(this);
// let the Hub module listen on Auth events
Hub.listen('auth', (data) => {
switch (data.payload.event) {
case 'signIn':
this.setState({authState: 'signedIn', authData: data.payload.data});
break;
case 'signIn_failure':
this.setState({authState: 'signIn', authData: null, authError: data.payload.data});
break;
default:
break;
}
});
this.state = {
authState: 'loading',
authData: null,
authError: null
}
}
componentDidMount() {
console.log('on component mount');
// check the current user when the App component is loaded
Auth.currentAuthenticatedUser().then(user => {
console.log(user);
this.setState({authState: 'signedIn'});
}).catch(e => {
console.log(e);
this.setState({authState: 'signIn'});
});
}
signOut() {
Auth.signOut().then(() => {
this.setState({authState: 'signIn'});
}).catch(e => {
console.log(e);
});
}
render() {
const { authState } = this.state;
return (
<div className="App">
{authState === 'loading' && (<div>loading...</div>)}
{authState === 'signIn' && <OAuthButton/>}
{authState === 'signedIn' && <button onClick={this.signOut}>Sign out</button>}
</div>
);
}
}
export default App;
In this form it is not working.
QA:
how it should looks like the OAuthSignIn method?
Because when I used the window.open method to redirect to the hosted ui url, my react app doesn't know that the user was authenticated, how the code should looks like, that the app could recognize that the login flow was finished successfully?

Is the React Context feature essentially a view model?

I'm giving the react context api a look, I decided to try setting it up to handle a login popup window:
import React, { Component } from 'react';
export const LoginContext = React.createContext();
import firebase from 'firebase';
export class LoginProvider extends Component {
state = {
loginOpen: false,
user: Object
}
render() {
return <LoginContext.Provider value={{
state: this.state,
showLogin: () => this.setState({
loginOpen: true
}),
closeLogin: () => this.setState({
loginOpen: false
}),
login: async (username, password) => {
var auth = firebase.auth();
try {
var that = this;
auth.signInWithEmailAndPassword(username, password).then((user)=>{
that.setState({user: user, loginOpen: false});
});
}
catch (E) {
return E;
}
}
}}>
{this.props.children}
</LoginContext.Provider>;
}
}
Used within a React component with the relevant functions being called.
It occurred to me that LoginProvider is a view model.
Which makes me wonder: Am I using the context feature correctly? Was this its intended usage?
If so why is the usage so archaic? What benefits does it provide? I mean it's less code than going through redux or sagas but still unnecessarily cumbersome.

Resources