Created
August 29, 2019 20:44
-
-
Save kalharbi/aa2ab48752b7e735a633bb0520973f3d to your computer and use it in GitHub Desktop.
User Authentication and Private Routes in React, React-Router, Redux, and Firebase
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
REACT_APP_DEV_API_KEY=XXX | |
REACT_APP_DEV_AUTH_DOMAIN=XXX | |
REACT_APP_DEV_DATABASE_URL=XXX | |
REACT_APP_DEV_PROJECT_ID=XXX | |
REACT_APP_DEV_STORAGE_BUCKET=XXX | |
REACT_APP_DEV_MESSAGING_SENDER_ID=XXX | |
REACT_APP_DEV_ID=XXX |
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, { useEffect } from 'react'; | |
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; | |
import PrivateRoute from './privateRoute'; | |
import { connect } from 'react-redux'; | |
import Home from './components/Home'; | |
import Navigation from './components/Navigation'; | |
import SignIn from './components/SignIn'; | |
import SignUp from './components/SignUp'; | |
import Dashboard from './components/Dashboard'; | |
import { authObserver } from './redux/actions/authActions'; | |
const App = (props) => { | |
useEffect(() => { | |
// subscribe to the auth observer | |
const unsubscribe = props.authObserver(); | |
// unsubscribe | |
return unsubscribe; | |
}); | |
return ( | |
<React.Fragment> | |
<h1>Auth App Example</h1> | |
<Router> | |
<Navigation /> | |
<Switch> | |
<Route exact path='/' component={Home} /> | |
<Route path='/signup' component={SignUp} /> | |
<Route path='/signin' component={SignIn} /> | |
<PrivateRoute path='/dashboard' component={Dashboard} /> | |
</Switch> | |
</Router> | |
</React.Fragment > | |
); | |
} | |
const mapDispatchToProps = dispatch => { | |
return { | |
authObserver: () => dispatch(authObserver()) | |
}; | |
} | |
export default connect(null, mapDispatchToProps)(App); |
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 AuthActionTypes from './authActionTypes'; | |
import firebase from '../../firebase'; | |
// Auth state observer. This is triggered only on sign-in or sign-out. | |
export const authObserver = () => (dispatch) => { | |
return firebase.auth().onAuthStateChanged(user => { | |
if (user) { | |
// User has signed in | |
dispatch({ | |
type: AuthActionTypes.SIGNIN_USER_SUCCESS, | |
payload: user | |
}); | |
} | |
else { | |
// User has signed out | |
dispatch({ | |
type: AuthActionTypes.SIGNOUT_USER_SUCCESS, | |
payload: null | |
}); | |
} | |
}); | |
}; | |
export const signUpAction = (newUser) => async (dispatch) => { | |
// The auth state listener/observer above will dispatch | |
// a sign in action when the sign in is successfull. | |
dispatch({ type: AuthActionTypes.SIGNUP_USER_REQUEST }); | |
try { | |
const userCredential = await firebase.auth() | |
.createUserWithEmailAndPassword(newUser.email, newUser.password); | |
await userCredential.user.updateProfile({ | |
displayName: newUser.name | |
}); | |
} | |
catch (err) { | |
dispatch({ | |
type: AuthActionTypes.SIGNUP_USER_ERROR, | |
err | |
}); | |
} | |
}; | |
export const signInAction = (user) => async (dispatch) => { | |
// The auth state listener/observer above will dispatch | |
// a sign in action when the sign in is successfull. | |
dispatch({ type: AuthActionTypes.SIGNIN_USER_REQUEST }); | |
try { | |
await firebase.auth() | |
.signInWithEmailAndPassword(user.email, user.password); | |
} | |
catch (err) { | |
dispatch({ | |
type: AuthActionTypes.SIGNIN_USER_ERROR, | |
err | |
}); | |
} | |
}; | |
export const signOutAction = () => async (dispatch) => { | |
// The auth state listener/observer above will dispatch | |
// a sign out action when the sign out is successfull. | |
dispatch({ type: AuthActionTypes.SIGNOUT_USER_REQUEST }); | |
try { | |
await firebase.auth().signOut(); | |
} | |
catch (err) { | |
dispatch({ | |
type: AuthActionTypes.SIGNOUT_USER_ERROR, | |
err | |
}); | |
}; | |
}; |
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
export const SIGNUP_USER_REQUEST = 'SIGNUP_USER_REQUEST'; | |
export const SIGNUP_USER_SUCCESS = 'SIGNUP_USER_SUCCESS'; | |
export const SIGNUP_USER_ERROR = 'SIGNUP_USER_ERROR'; | |
export const SIGNIN_USER_REQUEST = 'SIGNIN_USER_REQUEST'; | |
export const SIGNIN_USER_SUCCESS = 'SIGNIN_USER_SUCCESS'; | |
export const SIGNIN_USER_ERROR = 'SIGNIN_USER_ERROR'; | |
export const SIGNOUT_USER_REQUEST = 'SIGNOUT_USER_REQUEST'; | |
export const SIGNOUT_USER_SUCCESS = 'SIGNOUT_USER_SUCCESS'; | |
export const SIGNOUT_USER_ERROR = 'SIGNOUT_USER_ERROR'; |
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 AuthActionTypes from '../actions/authActionTypes'; | |
import firebase from '../../firebase'; | |
const INITIAL_STATE = { | |
isLoading: false, | |
authUser: firebase.auth().currentUser, | |
authError: null | |
}; | |
const authReducer = (state = INITIAL_STATE, action) => { | |
switch (action.type) { | |
// sign up actions | |
case AuthActionTypes.SIGNUP_USER_REQUEST: | |
return { | |
...state, | |
isLoading: true | |
}; | |
case AuthActionTypes.SIGNUP_USER_SUCCESS: | |
return { | |
...state, | |
isLoading: false, | |
authUser: action.payload | |
}; | |
case AuthActionTypes.SIGNUP_USER_ERROR: | |
return { | |
...state, | |
authUser: null, | |
isLoading: false, | |
authError: action.err.message | |
}; | |
// sign in actions | |
case AuthActionTypes.SIGNIN_USER_REQUEST: | |
return { | |
...state, | |
isLoading: true | |
}; | |
case AuthActionTypes.SIGNIN_USER_SUCCESS: | |
return { | |
...state, | |
isLoading: false, | |
authUser: action.payload | |
}; | |
case AuthActionTypes.SIGNIN_USER_ERROR: | |
return { | |
...state, | |
isLoading: false, | |
authUser: null, | |
authError: action.err.message | |
}; | |
// sign out actions | |
case AuthActionTypes.SIGNOUT_USER_REQUEST: | |
return { | |
...state, | |
isLoading: true | |
}; | |
case AuthActionTypes.SIGNOUT_USER_SUCCESS: | |
return { | |
...state, | |
isLoading: false, | |
authUser: null | |
}; | |
case AuthActionTypes.SIGNOUT_USER_ERROR: | |
return { | |
...state, | |
isLoading: false, | |
authError: action.err.message | |
}; | |
default: | |
return state; | |
} | |
} | |
export default authReducer; |
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 { createStore, applyMiddleware, compose } from 'redux'; | |
import rootReducer from '../reducers/rootReducer'; | |
import thunk from 'redux-thunk'; | |
const initialState = {}; | |
const configureStore = createStore( | |
rootReducer, | |
initialState, | |
compose( | |
applyMiddleware(thunk) | |
) | |
); | |
export default configureStore; |
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 { Redirect } from 'react-router-dom'; | |
import { connect } from 'react-redux'; | |
const Dashboard = (props) => { | |
if (!props.authUser) { | |
return (<Redirect to={{ | |
pathname: "/signin", | |
state: { from: props.location } | |
}} />); | |
} | |
return ( | |
<div> | |
<h1>Dashboard</h1> | |
<h2>Name: {props.authUser.displayName}</h2> | |
<h2>Email: {props.authUser.email}</h2> | |
</div> | |
); | |
}; | |
const mapStateToProps = (state) => { | |
return { authUser: state.auth.authUser }; | |
} | |
export default connect(mapStateToProps)(Dashboard); |
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 firebase from 'firebase/app'; | |
import 'firebase/auth'; | |
const firebaseConfig = { | |
apiKey: process.env.REACT_APP_DEV_API_KEY, | |
authDomain: process.env.REACT_APP_DEV_AUTH_DOMAIN, | |
databaseURL: process.env.REACT_APP_DEV_DATABASE_URL, | |
projectId: process.env.REACT_APP_DEV_PROJECT_ID, | |
storageBucket: process.env.REACT_APP_DEV_STORAGE_BUCKET, | |
messagingSenderId: process.env.REACT_APP_DEV_MESSAGING_SENDER_ID, | |
appId: process.env.REACT_APP_DEV_ID | |
}; | |
if (!firebase.apps.length) { | |
firebase.initializeApp(firebaseConfig); | |
} | |
export default firebase; |
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 { connect } from 'react-redux'; | |
const Home = (props) => { | |
return ( | |
<div> | |
<h1>Home</h1> | |
<h2>Is Authenticated? {props.authUser ? | |
"Yes" : "No"} | |
</h2> | |
</div> | |
); | |
}; | |
const mapStateToProps = (state) => { | |
return { | |
authUser: state.auth.authUser | |
}; | |
}; | |
export default connect(mapStateToProps)(Home); |
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 ReactDOM from 'react-dom'; | |
import { Provider } from 'react-redux'; | |
import store from './redux/store/configureStore'; | |
import App from './App'; | |
ReactDOM.render( | |
<Provider store={store}> | |
<App /> | |
</Provider> | |
, document.getElementById('root')); |
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 { Redirect, Route } from 'react-router-dom'; | |
import { connect } from "react-redux"; | |
const PrivateRoute = ({ component: Component, authUser, ...rest }) => { | |
return ( | |
<Route {...rest} render={props => | |
authUser ? | |
(<Component {...props} />) | |
: | |
(<Redirect | |
to={{ | |
pathname: "/signin", | |
state: { from: props.location } | |
}} | |
/> | |
) | |
} | |
/> | |
); | |
}; | |
const mapStateToProps = (state) => { | |
return { | |
authUser: state.auth.authUser | |
} | |
}; | |
export default connect(mapStateToProps)(PrivateRoute); |
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 { combineReducers } from 'redux'; | |
import authReducer from './authReducer'; | |
const rootReducer = combineReducers({ | |
auth: authReducer | |
}); | |
export default rootReducer; |
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, { useState } from 'react'; | |
import { Link, Redirect } from 'react-router-dom'; | |
import { connect } from 'react-redux'; | |
import { signInAction } from '../redux/actions/authActions'; | |
const SignIn = (props) => { | |
const [inputValues, setInputValues] = useState({ | |
email: '', password: '' | |
}); | |
const onSubmit = (event) => { | |
event.preventDefault(); | |
props.signin(inputValues); | |
}; | |
const onChange = (event) => { | |
const { name, value } = event.target; | |
setInputValues({ ...inputValues, [name]: value }); | |
} | |
const { authUser, authErr, isLoading } = props; | |
if (authUser) { | |
return (<Redirect to={{ | |
pathname: "/dashboard", | |
state: { from: props.location } | |
}} />); | |
} | |
return ( | |
<div> | |
<h1>Sign in</h1> | |
<p>Do not have account? <Link to='/signup'>Sign up here</Link></p> | |
<form onSubmit={onSubmit}> | |
<div> | |
<label htmlFor="email">Email</label> | |
<input type="email" name="email" required | |
value={inputValues.email} onChange={onChange} /> | |
</div> | |
<div> | |
<label htmlFor="password">Password</label> | |
<input type="password" name="password" required | |
value={inputValues.password} onChange={onChange} /> | |
</div> | |
<div> | |
<button>Sign in</button> | |
{isLoading && <div className="loader"></div>} | |
</div> | |
<div className="errorBox"> | |
{authErr && <p>Error: {authErr}</p>} | |
</div> | |
</form> | |
</div> | |
) | |
}; | |
const mapStateToProps = (state) => ( | |
{ | |
authUser: state.auth.authUser, | |
authErr: state.auth.authError, | |
isLoading: state.auth.isLoading | |
} | |
); | |
const mapDispatchToProps = (dispatch) => ( | |
{ | |
signin: (user) => dispatch(signInAction(user)) | |
} | |
); | |
export default connect(mapStateToProps, mapDispatchToProps)(SignIn); |
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 { connect } from 'react-redux'; | |
import { signOutAction } from '../redux/actions/authActions'; | |
const SignOutButton = (props) => { | |
if (!props.authUser) { | |
return null; | |
} | |
const onClick = (event) => { | |
event.preventDefault(); | |
props.signOut(); | |
} | |
return ( | |
<button className="signOutBtn" onClick={onClick}> | |
Sign out | |
</button> | |
); | |
}; | |
const mapStateToProps = (state) => { | |
return { authUser: state.auth.authUser }; | |
}; | |
const mapDispatchToProps = (dispatch) => { | |
return { | |
signOut: () => dispatch(signOutAction()) | |
}; | |
}; | |
export default connect(mapStateToProps, mapDispatchToProps)(SignOutButton); |
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, { useState } from 'react'; | |
import { Link, Redirect } from 'react-router-dom'; | |
import { compose } from 'redux'; | |
import { connect } from 'react-redux'; | |
import { signUpAction } from '../redux/actions/authActions'; | |
const SignUp = (props) => { | |
const [inputValues, setInputValues] = useState({ | |
name: '', email: '', password: '' | |
}); | |
const { authUser, authErr, isLoading } = props; | |
const onSubmit = (event) => { | |
event.preventDefault(); | |
props.signUp(inputValues); | |
} | |
const onChange = (event) => { | |
const { name, value } = event.target; | |
setInputValues({ ...inputValues, [name]: value }); | |
} | |
if (authUser) { | |
return (<Redirect to={{ | |
pathname: "/dashboard", | |
state: { from: props.location } | |
}} />); | |
} | |
return ( | |
<div> | |
<h1>Sign Up</h1> | |
<p>Already have account? <Link to='/signin'>Sign in here</Link></p> | |
<div> | |
<form onSubmit={onSubmit}> | |
<div> | |
<label htmlFor="name">Name</label> | |
<input type="text" name="name" required | |
value={inputValues.name} onChange={onChange} /> | |
</div> | |
<div> | |
<label htmlFor="email">Email</label> | |
<input type="email" name="email" required | |
value={inputValues.email} onChange={onChange} /> | |
</div> | |
<div> | |
<label htmlFor="password">Password</label> | |
<input type="password" name="password" required | |
value={inputValues.password} onChange={onChange} /> | |
</div> | |
<div> | |
<button>Sign up</button> | |
{isLoading && <div className="loader"></div>} | |
</div> | |
<div className="errorBox"> | |
{authErr && <p>Error: {authErr}</p>} | |
</div> | |
</form> | |
</div> | |
</div> | |
); | |
}; | |
const mapStateToProps = (state) => { | |
return { | |
authUser: state.auth.authUser, | |
authErr: state.auth.authError, | |
isLoading: state.auth.isLoading | |
}; | |
}; | |
const mapDispatchToProps = (dispatch) => { | |
return { | |
signUp: (newUser) => dispatch(signUpAction(newUser)) | |
} | |
}; | |
const enhance = compose( | |
connect(mapStateToProps, mapDispatchToProps) | |
); | |
export default enhance(SignUp); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment