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) 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.
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:
// 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)
Top comments (0)