React authentication with Firebase
Dawx
Posted on October 21, 2021
Hello everyone, in this guide I will show you how to set up basic authentication in React with Firebase. We will be also using react-router for creating routes (public and protected) and Redux ToolKit for saving user tokens to our applications state.
Project setup
First, we need to install React
npx create-react-app react-firebase
After it is installed we need to install dependencies we will use throughout this guide:
- React Router DOM:
npm install react-router-dom
- Firebase:
npm install firebase
- Redux and Redux Toolkit:
npm install react-redux
andnpm install @reduxjs/toolkit
After everything is installed you can start the local server:
cd react-firebase
npm start
If everything is alright you will get this screen:
Project structure
In the src folder, we will create four new folders (configs, pages, redux, and utils). Configs will contain configuration for Firebase. Pages will contain all our pages, I also created a subfolder auth which will contain all pages regarding user authentication. Redux folder will container redux store and slices. Utils folder is for utilities like protected route components.
Creating pages and routes
In pages->auth we will create 3 pages: Register, Login, Reset (password reset). I also created a folder „protected“ which has a page for authenticated users and a Home page for every user.
Login page
Below you can see basic React code for Login, it has two controlled inputs.
import React, { useState } from "react";
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleLogin = () => {
//here will go code for sign in
};
return (
<div>
<h1>Login</h1>
Email:
<br />
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<br />
Password:
<br />
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<br />
<button onClick={handleLogin}>Log In</button>
</div>
);
};
export default Login;
Register
import React, { useState } from "react";
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleRegister = () => {
//here will go code for sign up
};
return (
<div>
<h1>Register</h1>
Email:
<br />
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<br />
Password:
<br />
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<br />
<button onClick={handleRegister}>Register</button>
</div>
);
};
export default Login;
Password reset
import React, { useState } from "react";
const Reset = () => {
const [email, setEmail] = useState("");
const handleReset = () => {
//here will go code for password reset
};
return (
<div>
<h1>Reset password</h1>
Email:
<br />
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<br />
<button onClick={handleReset}>Reset password</button>
</div>
);
};
export default Reset;
The next step is to create links and routes for pages that will be in the App.js file. We can delete boilerplate code in App.js and write our own code.
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Login from "./pages/auth/Login";
import Register from "./pages/auth/Register";
import Reset from "./pages/auth/Reset";
import Home from "./pages/Home";
import Secret from "./pages/protected/Secret";
function App() {
return (
<Router>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/login">Login</Link>
</li>
<li>
<Link to="/register">Register</Link>
</li>
<li>
<Link to="/reset">Reset password</Link>
</li>
<li>
<Link to="/protected">Protected page</Link>
</li>
<li>
<Link to="#">Log out</Link>
</li>
</ul>
</nav>
<Switch>
<Route exact path="/register">
<Register />
</Route>
<Route exact path="/login">
<Login />
</Route>
<Route exact path="/reset">
<Reset />
</Route>
<Route exact path="/protected">
<Secret />
</Route>
<Route exact path="/">
<Home />
</Route>
</Switch>
</Router>
);
}
export default App;
First, we import react-router-dom dependencies and pages we just created. Then put <Router>
as the root component. Under that is created basic navigation, instead of <a>
element is used <Link>
which doesn't refresh page on click (that's the point of Single Page Applications). Under navigation is a switch where we declare routes and components which they render. Now our screen looks something like this:
The home page component is rendered at localhost:3000, if you click on the link in the navigation, other components will load without refreshing the page. Only Log Out doesn't render anything since it will be just used to log out.
Setting up Firebase
First, you need to create a Firebase account at https://firebase.google.com/ and go to the Firebase console at https://console.firebase.google.com. Click on „Add project“ and follow three simple steps.
After you finished with three steps you will be redirected to the screen which looks like on the picture below. Click on the </> icon to generate code for the web app.
Then enter the name of the app:
And then you get configuration for your app!
Now you can go to our project and in the config folder create file firebaseConfig.js. Paste config object and export it.
After creating the config it's time to initialize Firebase in our project, we do this in App.js. First, we need to import config from our file and initializeApp from firebase, then at the top of our component, we initialize it.
There is one last thing to do. We need to enable email and password authentication in the Firebase console. To do that go to your project, press on the „Authentication“ link on the left sidebar, then „Set up sign-in method“ in the middle of the screen. Click on email and password, enable it, and save.
With this Firebase setup is finished. In the next part, we will integrate existing forms in our project with Firebase to actually register, login, and log out users, as well as send password reset links.
Finish registration, login, log out and password reset
Registration
To register a user we need to import getAuth and createUserWithEmailAndPassword from firebase. getAuth gets us an instance of initialized auth service.
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
Now we can declare variable auth which will hold auth service. Next, we can use „createUserWithEmailAndPassword“ in our handleRegister, first argument is auth service, then email, and lastly password. We create a promise if registration is successful you will get the user object logged in the console, if it wasn't successful, an error will be logged.
const auth = getAuth();
const handleRegister = () => {
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
console.log("Registered user: ", user);
setEmail("");
setPassword("");
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.log("Error ocured: ", errorCode, errorMessage);
});
};
Here you can see the user object in the console after successful register:
Login
For the login page, we do the same, but this time we are using „signInWithEmailAndPassword“. Just like last time, import getAuth and this time signInWithEmailAndPassword. Here is a code snippet of signIn handler.
const signIn = () => {
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
console.log("Singed in user: ", user);
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.log("An error occured: ", errorCode, errorMessage);
});
};
Password reset
Repeat the steps for a password reset, but this time use sendPasswordResetEmail. This method only requires email. Here is a code snippet.
const handleReset = () => {
sendPasswordResetEmail(auth, email)
.then(() => {
console.log("success");
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.log("An error has occured: ", errorCode, errorMessage);
});
};
If it's successful you will get an email which will send you to page like this:
Log out
Since our navigation is directly in App.js, this is where we will implement log-out functionality. First import getAuth and signOut. Then add the following code to the „Log out“ link.
<Link
to="#"
onClick={() => {
signOut(auth)
.then(() => {
console.log("user signed out");
})
.catch((error) => {
console.log("error", error);
});
}}
>
Log out
</Link>
Setting up Redux Toolkit
In redux->slices folder create file authSlice.js. This file will save user in a global state and there will also be defined methods to manipulate with the state. Here is a code snippet:
import { createSlice } from "@reduxjs/toolkit";
const initialState = {};
export const authSlice = createSlice({
name: "user",
initialState,
reducers: {
saveUser: (state, action) => {
state.value = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const { saveUser } = authSlice.actions;
export default authSlice.reducer;
First, we import createSlice from RTK. Then initialize state as an empty object, next we create authSlice which has the name „user“, it has an initial state which is the empty object, and one reducer „saveUser“. saveUser takes two arguments, the first is a state which this slice has and the second is the action that will trigger it. It sets the value of the state to the payload of action (what you pass as an argument to that action). Lastly, we export saveUser and authSlice.
The next step is to set up a store which will hold state. In the root of redux folder create store.js file.
import { configureStore } from "@reduxjs/toolkit";
import authReducer from "./slice/authSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
},
});
Here we configure the store with one auth reducer. This is how your redux folder structure should look like now:
Now we need to provide a state from Redux to our app, to do that we need to wrap our component in index.js with the provider from redux which will use our store configuration.
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { store } from "./redux/store";
import { Provider } from "react-redux";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
The next step is to save the user token from firebase to our global state and update it every time something happens to the user. For that we will use onAuthStateChanged hook from firebase, every time auth changes we will save new user data to our global state. If there is no user, we just set a user to undefined.
import { getAuth, signOut, onAuthStateChanged } from "firebase/auth";
import { useSelector, useDispatch } from "react-redux";
import { saveUser } from "./redux/slice/authSlice";
function App() {
initializeApp(firebaseConfig);
const auth = getAuth();
const user = useSelector((state) => state.auth.value);
console.log("user from state", user);
const dispatch = useDispatch();
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
dispatch(saveUser(user.refreshToken));
} else {
dispatch(saveUser(undefined));
}
});
}, [auth, dispatch]);
Now if you go log in and log out, you will see this in your console:
This is is it for this part. In the next part, we will set up protected routes which will be accessible only for logged in users.
Protected routes
Credit to @medaminefh and his article https://dev.to/medaminefh/protect-your-components-with-react-router-4hf7 where I took the code changed it a bit for this project.
In utils folder create file ProtectedRoute.js and paste this code in:
import React from "react";
import { Redirect, Route } from "react-router";
import { useSelector } from "react-redux";
const ProtectedRoute = ({ component: Component }) => {
const user = useSelector((state) => state.auth.value);
console.log("user", user);
return (
<Route
render={(props) => {
if (user) {
return <Component {...props} />;
} else {
return <Redirect to="/" />;
}
}}
/>
);
};
export default ProtectedRoute;
ProtectedRoute takes in a component. First we „fetch“the user from the global state using useSelector hook, if the user exists provided component will render. Otherwise, the user will be redirected to the home page.
Now we can use the ProtectedRoute component in our App.js where routes are declared. First, import ProtectedRoute from utils and then simply replace which you want to protect with :
..
<Route exact path="/reset">
<Reset />
</Route>
<ProtectedRoute exact path="/protected" component={Secret} />
<Route exact path="/">
<Home />
</Route>
..
Now if you are logged in, you will be able to see the protected component, else you will be redirected to the homepage.
This is it for this tutorial, if you have any questions, feel free to ask!
You can find this repository here: https://github.com/PDavor/react-firebase to make it working just add your firebase configuration!
Posted on October 21, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.