├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── README.md
├── nest-cli.json
├── package.json
├── src
├── constants.ts
├── index.ts
├── interface
│ ├── index.ts
│ ├── object-literal.interface.ts
│ └── options.interface.ts
├── passport-firebase.strategy.ts
└── user.type.ts
├── tsconfig.build.json
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | sourceType: 'module',
6 | },
7 | plugins: ['@typescript-eslint/eslint-plugin'],
8 | extends: [
9 | 'plugin:@typescript-eslint/recommended',
10 | 'plugin:prettier/recommended',
11 | ],
12 | root: true,
13 | env: {
14 | node: true,
15 | jest: true,
16 | },
17 | ignorePatterns: ['.eslintrc.js'],
18 | rules: {
19 | '@typescript-eslint/interface-name-prefix': 'off',
20 | '@typescript-eslint/explicit-function-return-type': 'off',
21 | '@typescript-eslint/explicit-module-boundary-types': 'off',
22 | '@typescript-eslint/no-explicit-any': 'off',
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 |
13 | # OS
14 | .DS_Store
15 |
16 | # Tests
17 | /coverage
18 | /.nyc_output
19 |
20 | # IDEs and editors
21 | /.idea
22 | .project
23 | .classpath
24 | .c9/
25 | *.launch
26 | .settings/
27 | *.sublime-workspace
28 |
29 | # IDE - VSCode
30 | .vscode/*
31 | !.vscode/settings.json
32 | !.vscode/tasks.json
33 | !.vscode/launch.json
34 | !.vscode/extensions.json
35 |
36 | package-lock.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "arrowParens": "always",
5 | "printWidth": 100
6 | }
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [2.0.0] - 2021-04-26
9 |
10 | ### Added
11 | - add checkRevoked option for jwt verification
12 |
13 | ### Changed
14 |
15 | - Updated NestJS dependencies
16 | - Updated `firebase-admin` dependecy
17 | - Updated scripts. Now it's using [Nest Cli](https://docs.nestjs.com/cli/overview)
18 |
19 | [2.0.0]: https://github.com/tfarras/nestjs-firebase-auth/releases/tag/2.0.0
20 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | 1. [Fork it](https://help.github.com/articles/fork-a-repo/)
4 | 2. Install dependencies (`npm install`)
5 | 3. Create your feature branch (`git checkout -b my-new-feature`)
6 | 4. Commit your changes (`git commit -am 'Added some feature'`)
7 | 5. Push to the branch (`git push origin my-new-feature`)
8 | 6. [Create new Pull Request](https://help.github.com/articles/creating-a-pull-request/)
9 |
10 | ## Code Style
11 |
12 | We use [Prettier](https://prettier.io/) and eslint to maintain code style and best practices.
13 | Please make sure your PR adheres to the guides by running:
14 |
15 | ```bash
16 | npm run format
17 | ```
18 |
19 | and
20 |
21 | ```bash
22 | npm run lint
23 | ```
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 |
8 |
9 | NestJS Passport Strategy for Firebase Auth using Firebase Admin SDK
10 |
11 |
16 |
17 | # Installation
18 |
19 | ## Install peer dependencies
20 |
21 | ```bash
22 | npm install passport passport-jwt
23 | npm install --save-dev @types/passport-jwt
24 | ```
25 |
26 | ## Install `@nestjs/passport` for authentication
27 |
28 | ```bash
29 | npm install @nestjs/passport
30 | ```
31 |
32 | ## Install strategy
33 |
34 | ```bash
35 | npm install @tfarras/nestjs-firebase-auth
36 | ```
37 |
38 | # Important
39 |
40 | To work with Firebase Auth you need to configure and initialize your firebase app. For this purpose you can use my [module for firebase-admin](https://github.com/tfarras/nestjs-firebase-admin).
41 |
42 | # Create strategy
43 |
44 | ```typescript
45 | import { Injectable } from "@nestjs/common";
46 | import { PassportStrategy } from "@nestjs/passport";
47 | import { ExtractJwt } from 'passport-jwt';
48 | import { FirebaseAuthStrategy } from '@tfarras/nestjs-firebase-auth';
49 |
50 | @Injectable()
51 | export class FirebaseStrategy extends PassportStrategy(FirebaseAuthStrategy, 'firebase') {
52 | public constructor() {
53 | super({
54 | extractor: ExtractJwt.fromAuthHeaderAsBearerToken(),
55 | });
56 | }
57 | }
58 | ```
59 |
60 | Note: You should provide an extractor. More information about passport-jwt extractors you can find here: [http://www.passportjs.org/packages/passport-jwt/#included-extractors](http://www.passportjs.org/packages/passport-jwt/#included-extractors)
61 |
62 | # Create `AuthModule` and provide created strategy
63 |
64 | ```typescript
65 | import { Module } from "@nestjs/common";
66 | import { PassportModule } from "@nestjs/passport";
67 | import { FirebaseStrategy } from "./firebase.strategy";
68 |
69 | @Module({
70 | imports: [PassportModule],
71 | providers: [FirebaseStrategy],
72 | exports: [FirebaseStrategy],
73 | controllers: [],
74 | })
75 | export class AuthModule { }
76 | ```
77 |
78 | # Import `AuthModule` into `AppModule`
79 |
80 | ```typescript
81 | import { FirebaseAdminCoreModule } from '@tfarras/nestjs-firebase-admin';
82 | import { Module } from '@nestjs/common';
83 | import { ConfigModule, ConfigService } from 'nestjs-config';
84 | import { AppController } from './app.controller';
85 | import { AppService } from './app.service';
86 | import { AuthModule } from './auth/auth.module';
87 | import * as path from 'path';
88 |
89 | @Module({
90 | imports: [
91 | ConfigModule.load(path.resolve(__dirname, 'config', '**', '!(*.d).{ts,js}')),
92 | FirebaseAdminCoreModule.forRootAsync({
93 | useFactory: (config: ConfigService) => config.get('firebase'),
94 | inject: [ConfigService],
95 | }),
96 | AuthModule,
97 | ],
98 | controllers: [AppController],
99 | providers: [AppService],
100 | })
101 | export class AppModule { }
102 |
103 | ```
104 |
105 | # Protect your routes
106 |
107 | ```typescript
108 | import { Controller, Get, Inject, UseGuards } from '@nestjs/common';
109 | import { AuthGuard } from '@nestjs/passport';
110 | import { FirebaseAdminSDK, FIREBASE_ADMIN_INJECT } from '@tfarras/nestjs-firebase-admin';
111 | import { AppService } from './app.service';
112 |
113 | @Controller()
114 | export class AppController {
115 | constructor(
116 | private readonly appService: AppService,
117 | @Inject(FIREBASE_ADMIN_INJECT) private readonly fireSDK: FirebaseAdminSDK,
118 | ) { }
119 |
120 | @Get()
121 | @UseGuards(AuthGuard('firebase'))
122 | getHello() {
123 | return this.fireSDK.auth().listUsers();
124 | }
125 | }
126 |
127 | ```
128 |
129 | # Custom second validation
130 |
131 | In cases when you want to validate also if user exists in your database, or anything else after successfull Firebase validation you can define custom `validate` method in your strategy.
132 |
133 | ## Example
134 |
135 | ```typescript
136 | import { Injectable } from "@nestjs/common";
137 | import { PassportStrategy } from "@nestjs/passport";
138 | import { FirebaseAuthStrategy, FirebaseUser } from '@tfarras/nestjs-firebase-auth';
139 | import { ExtractJwt } from 'passport-jwt';
140 |
141 | @Injectable()
142 | export class FirebaseStrategy extends PassportStrategy(FirebaseAuthStrategy, 'firebase') {
143 | public constructor() {
144 | super({
145 | extractor: ExtractJwt.fromAuthHeaderAsBearerToken(),
146 | });
147 | }
148 |
149 | async validate(payload: FirebaseUser): Promise {
150 | // Do here whatever you want and return your user
151 | return payload;
152 | }
153 | }
154 | ```
155 |
--------------------------------------------------------------------------------
/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "collection": "@nestjs/schematics",
3 | "sourceRoot": "src"
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tfarras/nestjs-firebase-auth",
3 | "version": "2.0.0",
4 | "description": "NestJS Passport Strategy for Firebase Auth using Firebase Admin SDK",
5 | "author": "Taimoor Farras ",
6 | "readmeFilename": "README.md",
7 | "main": "dist/index.js",
8 | "license": "MIT",
9 | "files": [
10 | "dist/**/*",
11 | "*.md"
12 | ],
13 | "scripts": {
14 | "prebuild": "rimraf dist",
15 | "build": "nest build",
16 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
17 | "start": "nest start",
18 | "start:dev": "nest start --watch",
19 | "start:debug": "nest start --debug --watch",
20 | "start:prod": "node dist/main",
21 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
22 | "test": "jest",
23 | "test:watch": "jest --watch",
24 | "test:cov": "jest --coverage",
25 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
26 | "test:e2e": "jest --config ./test/jest-e2e.json"
27 | },
28 | "keywords": [
29 | "nestjs",
30 | "firebase",
31 | "firebase-admin",
32 | "firebase-auth",
33 | "passport",
34 | "passport-jwt",
35 | "auth",
36 | "authentication"
37 | ],
38 | "publishConfig": {
39 | "access": "public"
40 | },
41 | "repository": {
42 | "type": "git",
43 | "url": "git+https://github.com/tfarras/nestjs-firebase-auth.git"
44 | },
45 | "bugs": {
46 | "url": "https://github.com/tfarras/nestjs-firebase-auth/issues"
47 | },
48 | "homepage": "https://github.com/tfarras/nestjs-firebase-auth#readme",
49 | "peerDependencies": {
50 | "@nestjs/common": "^7.6.15",
51 | "@types/passport-jwt": "^3.0.5",
52 | "firebase-admin": "^9.6.0",
53 | "passport": "^0.4.1",
54 | "passport-jwt": "^4.0.0"
55 | },
56 | "devDependencies": {
57 | "@nestjs/cli": "^7.6.0",
58 | "@nestjs/common": "^7.6.15",
59 | "@nestjs/core": "^7.6.15",
60 | "@nestjs/platform-express": "^7.6.15",
61 | "@nestjs/schematics": "^7.3.0",
62 | "@nestjs/testing": "^7.6.15",
63 | "@types/express": "^4.17.11",
64 | "@types/jest": "^26.0.22",
65 | "@types/node": "^14.14.36",
66 | "@types/passport-jwt": "^3.0.5",
67 | "@types/supertest": "^2.0.10",
68 | "@typescript-eslint/eslint-plugin": "^4.19.0",
69 | "@typescript-eslint/parser": "^4.19.0",
70 | "eslint": "^7.22.0",
71 | "eslint-config-prettier": "^8.1.0",
72 | "eslint-plugin-prettier": "^3.3.1",
73 | "firebase-admin": "^9.6.0",
74 | "jest": "^26.6.3",
75 | "passport": "^0.4.1",
76 | "passport-jwt": "^4.0.0",
77 | "prettier": "^2.2.1",
78 | "reflect-metadata": "^0.1.13",
79 | "rimraf": "^3.0.2",
80 | "rxjs": "^6.6.6",
81 | "supertest": "^6.1.3",
82 | "ts-jest": "^26.5.4",
83 | "ts-loader": "^8.0.18",
84 | "ts-node": "^9.1.1",
85 | "tsconfig-paths": "^3.9.0",
86 | "typescript": "^4.2.3"
87 | },
88 | "jest": {
89 | "moduleFileExtensions": [
90 | "js",
91 | "json",
92 | "ts"
93 | ],
94 | "rootDir": "src",
95 | "testRegex": ".*\\.spec\\.ts$",
96 | "transform": {
97 | "^.+\\.(t|j)s$": "ts-jest"
98 | },
99 | "collectCoverageFrom": [
100 | "**/*.(t|j)s"
101 | ],
102 | "coverageDirectory": "../coverage",
103 | "testEnvironment": "node"
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const UNAUTHORIZED = 'Unauthorized';
2 | export const FIREBASE_AUTH = 'FIREBASE_AUTH';
3 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './constants';
2 | export * from './passport-firebase.strategy';
3 | export * from './user.type';
4 | export * from './interface';
5 |
--------------------------------------------------------------------------------
/src/interface/index.ts:
--------------------------------------------------------------------------------
1 | export * from './object-literal.interface';
2 | export * from './options.interface';
3 |
--------------------------------------------------------------------------------
/src/interface/object-literal.interface.ts:
--------------------------------------------------------------------------------
1 | export interface ObjectLiteral {
2 | [key: string]: any;
3 | }
4 |
--------------------------------------------------------------------------------
/src/interface/options.interface.ts:
--------------------------------------------------------------------------------
1 | import { JwtFromRequestFunction } from 'passport-jwt';
2 |
3 | export interface FirebaseAuthStrategyOptions {
4 | extractor: JwtFromRequestFunction;
5 | checkRevoked?: boolean;
6 | }
7 |
--------------------------------------------------------------------------------
/src/passport-firebase.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from '@nestjs/common';
2 | import { JwtFromRequestFunction } from 'passport-jwt';
3 | import { Strategy } from 'passport-strategy';
4 | import { Request } from 'express';
5 | import { FirebaseAuthStrategyOptions } from './interface/options.interface';
6 | import { UNAUTHORIZED, FIREBASE_AUTH } from './constants';
7 | import { FirebaseUser } from './user.type';
8 | import * as admin from 'firebase-admin';
9 |
10 | export class FirebaseAuthStrategy extends Strategy {
11 | readonly name = FIREBASE_AUTH;
12 | private checkRevoked = false;
13 |
14 | constructor(
15 | options: FirebaseAuthStrategyOptions,
16 | private extractor: JwtFromRequestFunction,
17 | private logger = new Logger(FirebaseAuthStrategy.name),
18 | ) {
19 | super();
20 |
21 | if (!options.extractor) {
22 | throw new Error(
23 | '\n Extractor is not a function. You should provide an extractor. \n Read the docs: https://github.com/tfarras/nestjs-firebase-auth#readme',
24 | );
25 | }
26 |
27 | this.extractor = options.extractor;
28 | this.checkRevoked = options.checkRevoked;
29 | }
30 |
31 | async validate(payload: FirebaseUser): Promise {
32 | return payload;
33 | }
34 |
35 | authenticate(req: Request): void {
36 | const idToken = this.extractor(req);
37 |
38 | if (!idToken) {
39 | this.fail(UNAUTHORIZED, 401);
40 |
41 | return;
42 | }
43 |
44 | try {
45 | admin
46 | .auth()
47 | .verifyIdToken(idToken, this.checkRevoked)
48 | .then((res) => this.validateDecodedIdToken(res))
49 | .catch((err) => {
50 | this.fail({ err }, 401);
51 | });
52 | } catch (e) {
53 | this.logger.error(e);
54 |
55 | this.fail(e, 401);
56 | }
57 | }
58 |
59 | private async validateDecodedIdToken(decodedIdToken: FirebaseUser) {
60 | const result = await this.validate(decodedIdToken);
61 |
62 | if (result) {
63 | this.success(result);
64 | }
65 |
66 | this.fail(UNAUTHORIZED, 401);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/user.type.ts:
--------------------------------------------------------------------------------
1 | import * as admin from 'firebase-admin';
2 |
3 | export type FirebaseUser = admin.auth.DecodedIdToken;
4 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "target": "es2017",
10 | "sourceMap": true,
11 | "outDir": "./dist",
12 | "baseUrl": "./",
13 | "incremental": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------