As Much as I know I have twice to learn
Authentication is a fundamental requirement for most web applications. In this tutorial, I'll walk you through implementing Firebase Authentication in a React application without using the Context API. We'll create sign-up and sign-in pages and set up navigation to a home page after successful authentication.
What We'll Build
- A React application with Firebase Authentication
- Sign-up page for new users
- Sign-in page for existing users
- Protected home page that requires authentication
- Simple navigation between these pages
Prerequisites
- Basic knowledge of React
- Node.js and npm installed on your computer
- A Google account to set up Firebase
Step 1: Set Up a New React Project
Let's start by creating a new React application:
npx create-react-app firebase-auth-app
cd firebase-auth-app
Step 2: Install Required Dependencies
We'll need to install Firebase and React Router:
npm install firebase react-router-dom
Step 3: Create a Firebase Project
- Go to the Firebase Console
- Click "Add project" and follow the setup instructions
- Once your project is created, click on the web icon (</>) to add a web app to your project
- Register your app with a nickname (e.g., "firebase-auth-app")
- Copy the Firebase configuration object that looks like this:
const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.appspot.com",
messagingSenderId: "your-messaging-sender-id",
appId: "your-app-id"
};
Step 4: Enable Authentication Methods in Firebase
- In the Firebase Console, navigate to "Authentication" in the left sidebar
- Click on "Get started"
- Enable "Email/Password" authentication by clicking on it and toggling the switch to "Enable"
- Click "Save"
Step 5: Set Up Firebase in Your React App
Create a new file src/firebase.js
to initialize Firebase:
// src/firebase.js
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const firebaseConfig = {
// Replace with your Firebase config object
apiKey: "your-api-key",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.appspot.com",
messagingSenderId: "your-messaging-sender-id",
appId: "your-app-id"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export default app;
Step 6: Create the Sign-Up Component
// src/components/Signup.js
import React, { useState } from 'react';
import { createUserWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../firebase';
import { Link, useNavigate } from 'react-router-dom';
function Signup() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
async function handleSubmit(e) {
e.preventDefault();
if (password !== confirmPassword) {
return setError('Passwords do not match');
}
try {
setError('');
setLoading(true);
await createUserWithEmailAndPassword(auth, email, password);
navigate('/');
} catch (error) {
setError('Failed to create an account: ' + error.message);
}
setLoading(false);
}
return (
<div className="signup-container">
<div className="signup-form">
<h2>Sign Up</h2>
{error && <div className="error-message">{error}</div>}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>Confirm Password</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
<button disabled={loading} type="submit" className="submit-button">
Sign Up
</button>
</form>
<div className="login-link">
Already have an account? <Link to="/login">Log In</Link>
</div>
</div>
</div>
);
}
export default Signup;
Step 7: Create the Login Component
// src/components/Login.js
import React, { useState } from 'react';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../firebase';
import { Link, useNavigate } from 'react-router-dom';
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
async function handleSubmit(e) {
e.preventDefault();
try {
setError('');
setLoading(true);
await signInWithEmailAndPassword(auth, email, password);
navigate('/');
} catch (error) {
setError('Failed to log in: ' + error.message);
}
setLoading(false);
}
return (
<div className="login-container">
<div className="login-form">
<h2>Log In</h2>
{error && <div className="error-message">{error}</div>}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button disabled={loading} type="submit" className="submit-button">
Log In
</button>
</form>
<div className="signup-link">
Need an account? <Link to="/signup">Sign Up</Link>
</div>
</div>
</div>
);
}
export default Login;
Step 8: Create the Home Component
// src/components/Home.js
import React, { useState, useEffect } from 'react';
import { onAuthStateChanged, signOut } from 'firebase/auth';
import { auth } from '../firebase';
import { useNavigate } from 'react-router-dom';
function Home() {
const [currentUser, setCurrentUser] = useState(null);
const navigate = useNavigate();
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setCurrentUser(user);
} else {
navigate('/login');
}
});
return () => unsubscribe();
}, [navigate]);
async function handleLogout() {
try {
await signOut(auth);
navigate('/login');
} catch (error) {
console.error('Failed to log out:', error);
}
}
if (!currentUser) return <div>Loading...</div>;
return (
<div className="home-container">
<h2>Welcome to Your Dashboard!</h2>
<p>You are logged in as: {currentUser.email}</p>
<button onClick={handleLogout} className="logout-button">
Log Out
</button>
</div>
);
}
export default Home;
Step 9: Create a Simple Auth Check Component
Instead of using the Context API, we'll create a simple component to check if a user is authenticated:
// src/components/AuthCheck.js
import React, { useState, useEffect } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from '../firebase';
import { Navigate } from 'react-router-dom';
function AuthCheck({ children }) {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setIsAuthenticated(!!user);
setIsLoading(false);
});
return () => unsubscribe();
}, []);
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return children;
}
export default AuthCheck;
Step 10: Set Up App Routing
Now, let's update the main App component to include all our routes:
// src/App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import AuthCheck from './components/AuthCheck';
import Signup from './components/Signup';
import Login from './components/Login';
import Home from './components/Home';
import './App.css';
function App() {
return (
<Router>
<div className="app-container">
<Routes>
<Route path="/" element={
<AuthCheck>
<Home />
</AuthCheck>
} />
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
</Routes>
</div>
</Router>
);
}
export default App;
Step 11: Add Basic CSS Styling
Let's add some minimal CSS to make our app look decent:
/* src/App.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
}
.app-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.signup-container,
.login-container,
.home-container {
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
width: 100%;
max-width: 400px;
}
h2 {
text-align: center;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.submit-button,
.logout-button {
width: 100%;
padding: 10px;
background-color: #4285f4;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
margin-top: 10px;
}
.submit-button:hover,
.logout-button:hover {
background-color: #357ae8;
}
.submit-button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.error-message {
background-color: #ffebee;
color: #c62828;
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
text-align: center;
}
.login-link,
.signup-link {
text-align: center;
margin-top: 15px;
}
.home-container p {
margin-bottom: 20px;
}
Step 12: Run and Test Your Application
Now let's run the application and test our authentication flow:
npm start
Your React application should open in your default browser at http://localhost:3000
.
Testing the Flow
Let's test our authentication flow to make sure everything works as expected:
-
Sign-up flow:
- Navigate to
/signup
- Create a new account with email and password
- After successful registration, you should be redirected to the home page
- Navigate to
-
Login flow:
- Log out from the home page
- You should be redirected to the login page
- Enter your credentials
- After successful login, you should be redirected to the home page
-
Authentication protection:
- Try accessing the home page (
/
) directly when not logged in - You should be redirected to the login page
- Try accessing the home page (
Understanding How It Works
Authentication Flow
Firebase Setup: We initialize Firebase and its authentication service in
firebase.js
.-
Sign Up:
- When a user submits the sign-up form, we call Firebase's
createUserWithEmailAndPassword
function - If successful, we navigate to the home page
- If there's an error, we display it to the user
- When a user submits the sign-up form, we call Firebase's
-
Login:
- When a user submits the login form, we call Firebase's
signInWithEmailAndPassword
function - If successful, we navigate to the home page
- If there's an error, we display it to the user
- When a user submits the login form, we call Firebase's
-
Authentication Check:
- We use Firebase's
onAuthStateChanged
to listen for authentication state changes - In the
AuthCheck
component, we redirect unauthenticated users to the login page - In the
Home
component, we use this to get the current user's information
- We use Firebase's
-
Logout:
- When a user clicks the logout button, we call Firebase's
signOut
function - After logging out, we redirect the user to the login page
- When a user clicks the logout button, we call Firebase's
Common Issues and Troubleshooting
Firebase Initialization Errors
If you see an error like "Firebase App named '[DEFAULT]' already exists", make sure you're only initializing Firebase once in your application.
Authentication Errors
- Check that you've enabled Email/Password authentication in the Firebase Console
- Make sure password meets Firebase's minimum requirements (at least 6 characters)
- Verify that you're using the correct Firebase configuration
Routing Issues
- Ensure all routes are properly defined in
App.js
- Verify that
AuthCheck
is correctly protecting your routes
Next Steps
Now that you have a basic authentication system in place, you might want to:
- Add password reset functionality
- Implement social media login options (Google, Facebook, etc.)
- Create a user profile page with more information
- Add email verification
- Implement more robust error handling
Conclusion
You've successfully built a React application with Firebase Authentication! You now have functional sign-up and sign-in pages that redirect users to a protected home page after successful authentication.
This implementation uses a straightforward approach without the Context API, making it easier to understand and maintain. You can build upon this foundation to create more complex applications with user authentication.
Happy coding!
Top comments (0)