Authentication with Firebase for a Vuejs project
Published: 2024-04-30 13:41:53

In my most recent Vuejs project, I had to decide how I would be going about the authentication feature of the app. Authentication is a task that is mainly left to the back-end/server, however, the browser or client does play a role in it too. Vue.js is a client-side framework, so I needed a server that could handle authentication. I opted for Firebase as a back-end solution and decided to store the user data in the Cloud Firestore. 

Firebase can take care of hashing passwords, sending confirmation emails, or storing the user’s data. The role of the front end during authentication is relatively simple. The Vue application sends the register/login details to the server, Firebase will then take care of the authentication and check if the user exists with the credentials that were sent. 

In case the authentication is successful, the server would respond with a token, and then this can be stored. It is important to store the token because it can be sent back to the server whenever data related to the user has to be modified or updated. Tokens are unique and only sending back the token to the server will suffice; we won’t have to send back all the authentication data. Firebase will not actively keep track of who is logged in, instead, we use the tokens to verify the user. This type of authentication is known as stateless authentication.

In this blog post I will be describing the steps I took that resulted in authenticating users successfully. As a quick overview, I first set up the Firebase SDK (software development kit) and initialised the app, then handled user registration with the help of ‘createUserWithEmailAndPassword’ method from Firebase. 

The Firebase SDK is a library that helps us communicate with Firebase’s products, so the first step I took was to install the SDK and initialise Firebase.

`npm install firebase`

Configuring and initialising Firebase can be done in the main.js file, however, to avoid cluttering the app, I decided to separate this into a different file. Instead of having all the configuration code in the main.js, I just had to import the file. I am using the Firebase modular API, which is designed to make your web app as small as possible by importing functions individually.

```javascript
//path to file: src/includes/firebase.js
import { initializeApp } from 'firebase/app'

//configuration object
const firebaseConfig = {
  apiKey: the api key will allow us to connect to Firebase,
  authDomain: url where we can send the authentication information,
  projectId: id of the project,
  storageBucket: describes the location where files are stored,
  appId: id of the app
}

//initialise the app
const firebaseApp = initializeApp(firebaseConfig)
```

I also imported the authentication module for the authentication services to be available. The authentication service of Firebase stores only the email and password, but it can’t hold any additional data such as name, age, or country. I added an input field requiring the entry of the user’s name to my registration form. To be able to store the name of the user I used the Firebase database (Firestore) service.

I created references to the authentication and Firestore services in the firebase.js file. I did this to not import and call repeatedly the same functions in multiple components. I also had multiple collections in Firestore, so I created a reference to the collection that held the user information. Below you can find the necessary imports and the final version of the firebase.js file.
Note: for all of this to work I had to enable the authentication services within the Firebase console, and made a few changes to the Cloud Firestore rules.

```javascript
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import {
  collection,
  getFirestore,
  initializeFirestore,
  persistentLocalCache
} from 'firebase/firestore'

const firebaseConfig = {
  apiKey: '',
  authDomain: '',
  projectId: '',
  storageBucket: '',
  appId: ''
}
const firebaseApp = initializeApp(firebaseConfig)
initializeFirestore(firebaseApp, { localCache: persistentLocalCache({}) })

//reference to authentication service
const auth = getAuth(firebaseApp)
//reference to the firestore service
const db = getFirestore(firebaseApp)
//reference to user collection
const usersCollection = collection(db, 'users')
export { auth, usersCollection, db}
```

Next, I handled the registration logic. I navigated to the component that has the registration form and listened to a submit event on the form. Once the event was triggered I wanted to call an asynchronous register function, which had a parameter that contained the input values (data from the input fields such as email, name, and password). 

The createUserWithEmailAndPassword function from Firebase accepts as arguments three elements, the auth instance, the user’s email, and the user’s password. Below you can see that I created the user with this function. Now that the user has been created, Firebase generates a unique user identifier (User UID) for it. I wanted this user identifier to be the document name in the 'users' collection, so I passed the UID to the doc() function. The doc() function gets a document reference instance that refers to a document at the specified path, the arguments passed to this function are the path segments. The setDoc() function writes to the document referred to by a document reference, and if the document does not exist in that case it will create it. The parameters that the setDoc() function accepts are the document reference and a map of the fields and values to be added. I wrapped this logic in a try-catch block.

```javascript
methods: {
   async register(values) {
      try {
      const userCred = await createUserWithEmailAndPassword(auth, values.email, values.password)
      const newDocument = doc(db, 'users', userCred.user.uid)
      await setDoc(newDocument, {
        name: values.name,
        email: values.email
      })
      } catch (error) {
        console.log(‘Unexpected error’)
        return
      }
    }
  }
```

Currently, the code is isolated to a single component, if you would want to register a user at a different location in your application it would make sense to refactor the code using Pinia actions.

Once a user is registered, or logged it is also very common that the dashboard or navigation links will slightly change to reflect the logged-in state. This was something that during my project I also wanted to achieve. I used Pinia state management and created a user store to keep track of the authentication state. I refactored the register function and added it to a Pinia store’s action function. I rendered conditionally different components/HTML elements or content in case the user was logged in.

Check out the refactored code in this blog post: https://dev.to/pentektimi/keeping-track-of-the-authentication-state-with-pinia-in-a-vuejs-web-app-3gb3, or find the full repository here: https://github.com/PentekTimi/podcast-listener.