Update: I have created a npm package that offers this functionality. Check it out here, or to install it directly:
npm install typescript-rest-fireauth
Original post:
I’ve been using the typescript-rest library recently to build backend APIs for few different projects. It’s quick to setup and uses annotations (decorators), which help to keep code clean.
I needed to secure one of the APIs, only allowing users who have authenticated with Firebase on the client side to make certain API requests. Typescript-rest has a built-in @Security decorator, but I struggled to understand how to use this passport-based decorator in conjunction with Firebase.
I decided to write my own decorator that would intercept requests, inspect headers for a Firebase JWT, and validate it using the Firebase Admin sdk. It utilizes typescript-rest’s ServiceContext, which contains a reference to the request (express.Request) object, and thus the headers. Here’s the decorator:
import { ServiceContext, Errors } from 'typescript-rest'; import * as admin from 'firebase-admin'; /** * Add authorization with Firebase ID token to a route * * @param target The prototype of the class * @param propertyKey The name of the method * @param descriptor The descriptor */ export function Auth () { return function (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor) { const originalMethod = descriptor.value; let serviceContext = null; descriptor.value = function () { return new Promise((resolve, reject)=>{ //locate the ServiceContext parameter for (var i=0;i { var result = originalMethod.apply(this, arguments); resolve(result); }) .catch((err) => { reject(new Errors.UnauthorizedError('Invalid Firebase token.')); }) } else { reject(new Errors.UnauthorizedError('Authorization required for this endpoint.')); } } else { console.log('Error: Authenticated decorator requires an instance of ServiceContext passed into the decorated method.'); reject(new Errors.InternalServerError()); } }); }; return descriptor; } }
You can then use the Auth decorator on any controller method/route, passing in the ServiceContext as a param, like this:
/** * Retrieve a User. * @param userId: The UID (Firebase) of the User */ @Auth() @Path(':uid') @GET get(@PathParam('uid') uid: string, @Context context: ServiceContext): Promise { return new Promise((resolve, reject)=>{ this.myService.getUser(uid) .then(function(user){ resolve(user); }) .catch(function(err){ reject(err); }); }); }
One thing I still need to do is to figure out how to send back a WWW-Authenticate header (per the HTTP Auth spec) when I reject with a 401. I tested and the Errors.UnauthorizedError does not do it automatically.
I tried to use, but i got this error “TS1238: Unable to resolve signature of class decorator when called as an expression.”
I’m using “es2015”, “es2017” libs
Hey Lucas. When you used the decorator, did you add parentheses, like this: @auth()
I think this error occurs if you leave them out.
Yes, i did had add parentheses, here my test project https://github.com/lucasdidur/rest-test
I think i know what happened, it’s just for methods, not for classes
Glad you figured it out. Let me know if the decorator ends up working for you.