Created
January 27, 2020 11:37
-
-
Save valentinalexeev/594fb2e1268851c60a9252b777655a63 to your computer and use it in GitHub Desktop.
React-admin v3 Firebase Auth + FirebaseUI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The AuthProvider also uses custom claims in idToken to manage granular permissions.