Skip to content

Instantly share code, notes, and snippets.

@valentinalexeev
Created January 27, 2020 11:37
Show Gist options
  • Save valentinalexeev/594fb2e1268851c60a9252b777655a63 to your computer and use it in GitHub Desktop.
Save valentinalexeev/594fb2e1268851c60a9252b777655a63 to your computer and use it in GitHub Desktop.
React-admin v3 Firebase Auth + FirebaseUI
import * as firebase from 'firebase';
import 'firebase/auth';
import lodash from 'lodash';
const baseConfig = {
userProfilePath: 'users/',
userAdminProp: 'isAdmin',
requireAdmin: true,
};
export const AuthProvider = (customConfig = {}) => {
const initialConfig = {
...baseConfig,
...customConfig
};
const authProvider = {
config: initialConfig,
isSignedIn: false,
currentUser: null,
login (params) {
const { currentUser } = params;
console.log(`authProvider: login`, currentUser);
this.currentUser = currentUser;
return new Promise((resolve, reject) => {
this.loadProfile(this.currentUser, resolve, reject);
});
},
logout (params) {
console.log(`authProvider: logout`, params);
if (this.isSignedIn) {
return new Promise((resolve, reject) => {
firebase.auth().signOut().then(() => {
console.log(`authProvider: user signed-out`);
this.isSignedIn = false;
resolve();
})
.catch(reject)
});
} else {
// if we are not logged in - nothing to logout
return Promise.resolve();
}
},
checkAuth (params) {
console.log(`authProvider: checkAuth`, params, this.isSignedIn);
if (this.isSignedIn) {
console.log(`authProvider: already signed in`);
return Promise.resolve(this.currentUser);
} else {
if (firebase.auth().currentUser) {
console.log(`authProvide: firebase thinks we are logged in, refreshing state`);
this.currentUser = firebase.auth().currentUser;
this.isSignedIn = true;
return Promise.resolve(this.currentUser);
} else {
console.log(`authProvider: currentUser is missing`);
return Promise.reject();
}
}
},
checkError (error) {
console.log(`authProvider: checkError`, error);
return Promise.resolve()
},
getPermissions (params) {
if (firebase.auth().currentUser) {
return new Promise((resolve) => {
firebase.auth().currentUser.getIdTokenResult(true).then((idTokenResult) => {
resolve({
permissions: idTokenResult.claims.permissions,
has (permission, trueValue = true, falseValue = false) {
if (lodash.indexOf(idTokenResult.claims.permissions, permission) !== -1) {
console.log(`permission ${permission} is in custom claims.`);
return trueValue;
}
console.log(`permission ${permission} is not in custom claims.`);
return falseValue;
}
});
});
});
} else {
return Promise.reject();
}
},
// non-standard methods
loadProfile (user, resolve, reject) {
console.log(`Will fetch profile ${this.config.userProfilePath + user.uid}`, user);
firebase.firestore().doc(this.config.userProfilePath + user.uid).get()
.then((snapshot) => {
if (snapshot.exists) {
const profile = snapshot.data();
if (this.config.requireAdmin && !profile[this.config.userAdminProp]) {
// user is not admin when we require him to be one
console.warn(`authProvider: existing user but not admin which is required`);
this.isSignedIn = false;
reject && reject();
} else {
// user is registered (has data under users) and we do not require admins
this.isSignedIn = true;
resolve && resolve();
}
} else {
// user is unknown
console.warn(`authProvider: unknown user`);
this.isSignedIn = false;
}
});
}
};
return authProvider;
}
import React from 'react';
import { useLogin, useNotify, Login } from 'react-admin';
import { useAuthState } from 'react-firebase-hooks/auth';
import CircularProgress from '@material-ui/core/CircularProgress';
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
export const LoginPage = ({ redirectTo }) => {
const login = useLogin();
const notify = useNotify();
const [ user, loading ] = useAuthState(firebase.auth());
const uiConfig = {
signInFlow: "popup",
signInOptions: [
{
provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID,
defaultCountry: "CY",
recaptchaParameters: {
type: 'image', // 'audio'
size: 'normal', // 'invisible' or 'compact'
badge: 'bottomleft' //' bottomright' or 'inline' applies to invisible.
},
},
firebase.auth.EmailAuthProvider.PROVIDER_ID,
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.FacebookAuthProvider.PROVIDER_ID
],
signInSuccessUrl: redirectTo,
callbacks: {
signInSuccessWithAuthResult: () => {
return false;
},
signInFailure: (error) => {
console.error(error);
notify(`Unable to sign-in`, 'warn');
}
}
};
// use effect instead of UI redirect to catch both re-auth and auth cases
React.useEffect(() => {
if (user !== null) {
login({currentUser: user});
}
}, [user, login]);
return (
<Login>
{ loading && <CircularProgress/> }
{ !loading && !user && <StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={firebase.auth()}/> }
</Login>
);
};
@valentinalexeev
Copy link
Author

The AuthProvider also uses custom claims in idToken to manage granular permissions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment