DEV Community

CK
CK

Posted on • Originally published at blog.kapic.io on

Authentication with React and Firebase

Authentication is something that most public applications will need, and it is a pain to configure, so I figured documenting how I do authentication with React and Firebase will be helpful for others (and for myself when I inevitably forget a step). I will update this blog as my process for authentication changes. Also, my initial strategy is derived from h3webdevtut's tutorial.

This tutorial assumes you already have created a Firebase project, a web app for the project, and you have a React project set up.

For a quick copy/paste, scroll to the bottom of this page.

1. Install firebase

npm install firebase
Enter fullscreen mode Exit fullscreen mode

2. Add your firebaseConfig

Create a file called /src/fire.js.

// firebaseConfig at /src/fire.js
import firebase from 'firebase'

const firebaseConfig = {
    apiKey: 'yourAPIKey',
    authDomain: 'yourAuthDomain',
    databaseURL: 'yourDatabaseURL',
    projectId: 'yourProjectId',
    storageBucket: 'yourStorageBucket',
    messagingSenderId: 'yourMessagingSenderId',
    appId: 'yourAppId'
}

const fire = firebase.initializeApp(firebaseConfig);

export default fire;
Enter fullscreen mode Exit fullscreen mode

Optional (will update later): You can put your firebaseConfig variables in a .env file.

3. Enable Authentication method in the Firebase Console

In the Firebase console, enable the email and password sign-in method.

4. Imports, Create state variables

In /src/App.js (or whatever file your app is in), import useState and useEffect:

import React, { useState, useEffect } from 'react'
import fire from './fire'
Enter fullscreen mode Exit fullscreen mode

Add states for keeping track of the user:

const App () => {
    const [user, setUser] = useState('');
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [emailError, setEmailError] = useState('');
    const [passwordError, setPasswordError] = useState('');
    const [hasAccount, setHasAccount] = useState(false);

    ...
}
Enter fullscreen mode Exit fullscreen mode

5. Create functions to handle events

Define two helper functions to clear inputs and errors:

clearInputs():

const clearInputs = () => {
    setEmail('');
    setPassword('');
}
Enter fullscreen mode Exit fullscreen mode

clearErrors():

const clearErrors = () => {
    setEmailError('');
    setPasswordError('');
}
Enter fullscreen mode Exit fullscreen mode

handleLogin() for when a user logs in:

const handleLogin = () => {
    clearErrors();
    fire
        .auth()
        .signInWithEmailAndPassword(email, password)
        .catch(err => {
            // user firebase err code to determine kind of error
            // and handle it accordingly
            switch(err.code) {
                case "auth/invalid-email":
                case "auth/user-disabled":
                case "auth/user-not-found":
                    setEmailError(err.message);
                    break;
                case "auth/wrong-password":
                    setPasswordError(err.message);
                    break;
            }
        })
}
Enter fullscreen mode Exit fullscreen mode

handleSignup() for when a user signs up:

const handleSignup = () => {
    clearErrors();
    fire
        .auth()
        .createUserWithEmailAndPassword(email, password)
        .catch(err => {
            // user firebase err code to determine kind of error
            // and handle it accordingly
            switch(err.code) {
                case "auth/email-already-in-use":
                case "auth/invalid-email":
                    setEmailError(err.message);
                    break;
                case "auth/weak-password":
                    setPasswordError(err.message);
                    break;
            }
        })
}
Enter fullscreen mode Exit fullscreen mode

handleLogout() for when a user logs out:

const handleLogout = () => {
    fire.auth().signOut();
}
Enter fullscreen mode Exit fullscreen mode

authListener() for updating user state:

const authListener = () => {
    fire.auth().onAuthStateChanged(user => {
        if (user) {
            clearInputs();
            setUser(user);
        } else {
            setUser('');
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

Also, call authListener() with a useEffect():

useEffect(() => {
    authListener();
}, [])
Enter fullscreen mode Exit fullscreen mode

At this point, /src/App.js should look something like this:

import React, { useState, useEffect } from 'react'
import fire from './fire'

const App () => {
    const [user, setUser] = useState('');
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [emailError, setEmailError] = useState('');
    const [passwordError, setPasswordError] = useState('');
    const [hasAccount, setHasAccount] = useState(false);

    const clearInputs = () => {
        setEmail('');
        setPassword('');
    }

    const clearErrors = () => {
        setEmailError('');
        setPasswordError('');
    }

    const handleLogin = () => {
        clearErrors();
        fire
            .auth()
            .signInWithEmailAndPassword(email, password)
            .catch(err => {
                // use firebase err code to determine kind of error
                // and handle it accordingly
                switch(err.code) {
                    case "auth/invalid-email":
                    case "auth/user-disabled":
                    case "auth/user-not-found":
                        setEmailError(err.message);
                        break;
                    case "auth/wrong-password":
                        setPasswordError(err.message);
                        break;
                }
            })
    }

    const handleSignup = () => {
        clearErrors();
        fire
            .auth()
            .createUserWithEmailAndPassword(email, password)
            .catch(err => {
                // use firebase err code to determine kind of error
                // and handle it accordingly
                switch(err.code) {
                    case "auth/email-already-in-use":
                    case "auth/invalid-email":
                        setEmailError(err.message);
                        break;
                    case "auth/weak-password":
                        setPasswordError(err.message);
                        break;
                }
            })
    }

    const handleLogout = () => {
        fire.auth().signOut();
    }

    const authListener = () => {
        fire.auth().onAuthStateChanged(user => {
            if (user) {
                clearInputs();
                setUser(user);
            } else {
                setUser('');
            }
        })
    }

    useEffect(() => {
        authListener();
    }, [])

    ...
}
Enter fullscreen mode Exit fullscreen mode

6. Create Login component

Create a component for the login page. For example, /src/components/Login.js.

import React from 'react'

const Login = (props) => {
    const {
        email,
        setEmail,
        password,
        setPassword,
        handleLogin,
        handleSignup,
        hasAccount,
        setHasAccount,
        emailError,
        passwordError
    } = props;

    return (
        <section>
            <div>
                <label>Username</label>
                <input type="text" autoFocus required value={email} onChange={e => setEmail(e.target.value)}/>
                <p>{emailError}</p>
                <label>Password</label>
                <input type="text" required value={password} onChange={e => setPassword(e.target.value)}/>
                <p>{passwordError}</p>
                <div>
                    { hasAccount ? (
                        <>
                        <button onClick={() => handleLogin()}>Sign In</button>
                        <p>Don't have an account? <span onClick={setHasAccount(!hasAccount)}>Sign up</span></p>
                        </>
                    ) : (
                        <>
                        <button onClick={() => handleSignup()}>Sign Up</button>
                        <p>Already have an account? <span onClick={setHasAccount(!hasAccount)}>Sign in</span></p>
                        </>
                    )}
                </div>
            </div>
        </section>
    )
}

export default Login;
Enter fullscreen mode Exit fullscreen mode

The above component is the barebones code you need to handle user signups and logins. You will have to add your own styling.

7. Add the Login component to your App

In /src/App.js, import the Login component:
import Login from ./components/Login

And in the function for the App component, return the Login component conditionally on user:

const App = () => {
    ...

    return (
        {hasAccount ? (
            <Login
            email={email}
            setEmail={setEmail}
            password={password}
            setPassword={setPassword}
            handleLogin={handleLogin}
            handleSignup={handleSignup}
            hasAccount={hasAccount}
            setHasAccount={setHasAccount}
            emailError={emailError}
            passwordError={passwordError}
            />
        ) : (

        )}
    )
}
Enter fullscreen mode Exit fullscreen mode

8. Things to note

This tutorial does not (yet) include how to store user ID's in a database, nor does it show how to handle welcome and forgot password emails. I plan on adding those in the future, as I have time and understand Firebase authentication better.

If you notice an error in this blog post, leave a comment and I will address it. Thank you!

All code

For a quick copy/paste.

/src/App.js

import React, { useState, useEffect } from 'react'
import fire from './fire'

const App () => {
    const [user, setUser] = useState('');
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [emailError, setEmailError] = useState('');
    const [passwordError, setPasswordError] = useState('');
    const [hasAccount, setHasAccount] = useState(false);

    const clearInputs = () => {
        setEmail('');
        setPassword('');
    }

    const clearErrors = () => {
        setEmailError('');
        setPasswordError('');
    }

    const handleLogin = () => {
        clearErrors();
        fire
            .auth()
            .signInWithEmailAndPassword(email, password)
            .catch(err => {
                // use firebase err code to determine kind of error
                // and handle it accordingly
                switch(err.code) {
                    case "auth/invalid-email":
                    case "auth/user-disabled":
                    case "auth/user-not-found":
                        setEmailError(err.message);
                        break;
                    case "auth/wrong-password":
                        setPasswordError(err.message);
                        break;
                }
            })
    }

    const handleSignup = () => {
        clearErrors();
        fire
            .auth()
            .createUserWithEmailAndPassword(email, password)
            .catch(err => {
                // use firebase err code to determine kind of error
                // and handle it accordingly
                switch(err.code) {
                    case "auth/email-already-in-use":
                    case "auth/invalid-email":
                        setEmailError(err.message);
                        break;
                    case "auth/weak-password":
                        setPasswordError(err.message);
                        break;
                }
            })
    }

    const handleLogout = () => {
        fire.auth().signOut();
    }

    const authListener() => {
        fire.auth().onAuthStateChanged(user => {
            if (user) {
                clearInputs();
                setUser(user);
            } else {
                setUser('');
            }
        })
    }

    useEffect(() => {
        authListener();
    }, [])

    return (
        {hasAccount ? (
            <Login
            email={email}
            setEmail={setEmail}
            password={password}
            setPassword={setPassword}
            handleLogin={handleLogin}
            handleSignup={handleSignup}
            hasAccount={hasAccount}
            setHasAccount={setHasAccount}
            emailError={emailError}
            passwordError={passwordError}
            />
        ) : (

        )}
    )
}
Enter fullscreen mode Exit fullscreen mode

/src/components/Login.js

import React from 'react'

const Login = (props) => {
    const {
        email,
        setEmail,
        password,
        setPassword,
        handleLogin,
        handleSignup,
        hasAccount,
        setHasAccount,
        emailError,
        passwordError
    } = props;

    return (
        <section>
            <div>
                <label>Username</label>
                <input type="text" autoFocus required value={email} onChange={e => setEmail(e.target.value)}/>
                <p>{emailError}</p>
                <label>Password</label>
                <input type="text" required value={password} onChange={e => setPassword(e.target.value)}/>
                <p>{passwordError}</p>
                <div>
                    { hasAccount ? (
                        <>
                        <button onClick={() => handleLogin()}>Sign In</button>
                        <p>Don't have an account? <span onClick={setHasAccount(!hasAccount)}>Sign up</span></p>
                        </>
                    ) : (
                        <>
                        <button onClick={() => handleSignup()}>Sign Up</button>
                        <p>Already have an account? <span onClick={setHasAccount(!hasAccount)}>Sign in</span></p>
                        </>
                    )}
                </div>
            </div>
        </section>
    )
}

export default Login;
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Top comments (0)