Skip to content

Instantly share code, notes, and snippets.

@kalharbi
Created August 29, 2019 20:44
Show Gist options
  • Save kalharbi/aa2ab48752b7e735a633bb0520973f3d to your computer and use it in GitHub Desktop.
Save kalharbi/aa2ab48752b7e735a633bb0520973f3d to your computer and use it in GitHub Desktop.
User Authentication and Private Routes in React, React-Router, Redux, and Firebase
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
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);
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
});
};
};
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';
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;
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;
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);
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;
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);
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'));
import React from 'react';
import { Link } from 'react-router-dom';
import SignOut from './SignOut';
export default () => {
return (
<ul>
<li><Link to='/signup'>Sign up</Link></li>
<li><Link to='/signin'>Sign in</Link></li>
<li><Link to='/dashboard'>Dashboard</Link></li>
<li><SignOut /></li>
</ul>
);
};
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);
import { combineReducers } from 'redux';
import authReducer from './authReducer';
const rootReducer = combineReducers({
auth: authReducer
});
export default rootReducer;
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);
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);
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