Easy Step by Step Loopback 4 JWT Authentication with Firebase
Tags: javascript, firebase, authentication, loopback
Published: 2021-10-11 13:47:29

The title says it all. The team decided to use Loopback 4 for the APIs as it is easy to get something working really quick. The challenge came when we wanted to integrate the authentication mechanism with our front end which was on VueJs. Firebase was our authentication server since we only needed Social logins and nothing more. Firebase takes a lot of the pain out of getting an web app with authentication up, well done Firebase team!

Back to the matter at hand. Loopback 4 documentation had sections on using JWT as well as custom authentication strategies. However, it was unclear and we were really stuck for many days on how to get it working. I would like to detail out the steps we took to get it working, much as a reference to my future self and hoping to help those in similar situations.

Let's scaffold a Loopback 4 application. I use Ubuntu in WSL 2 as my primary development environment. I also use `yarn` when the scaffold asks.

```
$ lb4 mysuperapp
```

Answer the questions and wait for the scaffold to finish. You then need to add the `firebase-admin, @loopback/authentication and @loopback/authentication-jwt` package to your Loopback application.

```
$ cd mysuperapp
$ yarn add firebase-admin @loopback/authentication @loopback/authentication-jwt
```

Follow the instructions at [Add the Firebase Admin SDK to your server (google.com)](https://firebase.google.com/docs/admin/setup) to finish setting up the admin SDK. You will need to save the JSON file with your private key to your local machine and add it to the Loopback app. I usually save it into a folder under the app root called ./keys/ and I add this folder to my `.gitignore` file so as to avoid checking in the secret file.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bb88et9vgo5z4o15qmlf.png)

The next step is **IMPORTANT** to ensure you get your Firebase SDK setup properly. You need to have an environment variable called `GOOGLE_APPLICATION_CREDENTIALS` defined. The value is the path to the JSON file you downloaded from Firebase earlier. You have to ensure this environment variable is present **every time** before you run you Loopback app. In Linux you would do *(replace the path and file name based on the file you downloaded earlier)*:

```
// you can do it this way
$ export GOOGLE_APPLICATION_CREDENTIALS="./keys/my_secret_file.json"
$ yarn start
// or this way before you run the app
$ GOOGLE_APPLICATION_CREDENTIALS="./keys/my_secret_file.json" yarn start
```
Next step is to initialize Firebase. Open `application.ts`, import the `firebase-admin` and `loopback-authentication` packages in the `constructor` . Next add the Firebase initialization steps. You will need your Firebase project Id and you can get that from the project settings in the Firebase console.

```
// application.ts
import * as firebase from "firebase-admin";
import { AuthenticationComponent } from '@loopback/authentication';
import { JWTAuthenticationComponent, TokenServiceBindings } from '@loopback/authentication-jwt';
export class MysuperappApplication extends BootMixin(
  ServiceMixin(RepositoryMixin(RestApplication)),
) {
  constructor(options: ApplicationConfig = {}) {
    super(options);
    // initialize firebase 
    firebase.initializeApp({
      credential: firebase.credential.applicationDefault(),
      projectId: 'my-firebase-project'
    })
```

We then add the JWT component as shown in the Loopback documentation here [How to secure your LoopBack 4 application with JWT authentication](https://loopback.io/doc/en/lb4/Authentication-tutorial.html): 

```
// application.ts - Add this at the bottom of the constructor
this.component(AuthenticationComponent);
this.component(JWTAuthenticationComponent);
```

The code above will add the Authentication and JWT component to your Loopback application. That’s it. How cool is that! The last step before actually handling the Firebase code is to tell Loopback where to go for authentication. We do that by `binding` the `TOKEN_SERVICE` to our class that will handle decoding the Firebase token.

```
// application.ts - add this after adding the 2 lines above
this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(FirebaseTokenService);
```
At this point you will get an error as we have not defined the class yet. Let’s do that next. Open the terminal in your application folder.

```
mysuperapp$ lb4 service
? Service type: Local service class bound to application context
? Service name: FirebaseToken
   create src/services/firebase-token.service.ts
   update src/services/index.ts
Service FirebaseToken was/were created in src/services
```
Import this file in `application.ts`

```
import * as firebase from "firebase-admin";
import { FirebaseTokenService } from './services';
```

Let’s setup the `FirebaseTokenService`. We have to implement the `TokenService` interface. Since we won’t be generating any tokens we throw an error when anyone tries to use that function

```
// firebase-token.service.ts
// Let's define an inline error class to that Loopback 
// can properly inform the user 
class FirebaseTokenError extends Error {
  statusCode: number
  constructor(message: string, statusCode = 403) {
    super(message)
    this.statusCode = statusCode;
  }
}
@injectable({scope: BindingScope.TRANSIENT})
export class FirebaseTokenService implements TokenService {
  constructor( ) { }
  async verifyToken (token: string): Promise<UserProfile> {
     // TODO implement the token decode and verify
  }
  async generateToken (userProfile: UserProfile): Promise<string> {
    throw new FirebaseTokenError("This service is not implemented");
  }
}
```

The next few steps are straight forward and you can get the details by reading through the Firebase documentation. Let’s decode the token and return the `UserProfile` expected by Loopback. First add the `firebase-admin` library to your `FirebaseTokenService`.

```
// firebase-token.service.ts
import * as firebaseAdmin from "firebase-admin";
```

Next implement the function to decode the token and return the `UserProfile`. Both of these functions should be defined in your `FirebaseTokenService` class.

```
// firebase-token.service.ts
async verifyToken (token: string): Promise<UserProfile> {
   // call the admin sdk to decode the token
    const decodedToken = await firebaseAdmin
       .auth()
       .verifyIdToken(token);
   // I cast to Record<string, any> here as I need to make 
   // some changes to the object
   let userProfile: Record<string, any> = decodedToken;
   // call function to return the UserProfile from 
   // decoded token
   return this.tokenToUserProfile(userProfile);
}
/**
 * Function to convert token to UserProfile
 */
tokenToUserProfile (token: Record<string, any>): UserProfile {
   return {
     [securityId]: token.user_id,
     email: token.email,
     name: token.name,
     picture: token.picture,
     uid: token.user_id,
  }
}
```

With that, you now have a fully functioning integration between your Loopback application and Firebase authentication. You can view the full code at my GitHub (https://github.com/alfonsojohan/loopback4-firebase)