Skip to content

Commit d0541ba

Browse files
committed
add: firebase admin sdk token verification
client generates token after sucessful login, sends the token to server for jwt token creation ↓ server through admin sdk, validates that token, then proceedes to generate jwt token, else unauthorized error. ↓ client uses that token to make req to server.
1 parent cec7582 commit d0541ba

File tree

8 files changed

+752
-22
lines changed

8 files changed

+752
-22
lines changed

.example.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ COLLECTION=''
1111
RATE_LIMIT_WINDOW_MS=10
1212
RATE_LIMIT_MAX=5
1313
JWT_SECRET='placeholder-secret'
14+
FIREBASE_DATABASE_URL=''
15+
FIREBAE_SERVICE_ACCOUNT=''

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ logs/
55
debug.log
66
yarn-error.log
77
.env
8-
/cache/
8+
/cache/
9+
serviceAccou*

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"express-handlebars": "^5.2.0",
5555
"express-jwt": "^6.0.0",
5656
"express-rate-limit": "^5.2.3",
57+
"firebase-admin": "^9.11.0",
5758
"jsonwebtoken": "^8.5.1",
5859
"mongodb": "^3.6.2",
5960
"morgan": "^1.10.0",

src/app.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ import bodyParser from 'body-parser';
66
import exphbs from 'express-handlebars';
77
import lusca from 'lusca';
88
import jwt from 'express-jwt';
9+
import * as admin from 'firebase-admin';
910

10-
import { JWT_SECRET, PORT } from './util/secrets';
11+
import { FIREBASE_DATABASE, JWT_SECRET, PORT } from './util/secrets';
1112
import * as homeController from './controllers/home';
1213
import * as apiController from './controllers/api';
1314
import * as unmatchedController from './controllers/unmatched';
1415
import * as authenticationController from './controllers/auth';
1516
import { limitrequest } from './util/ratelimit';
1617
import { unauthorizedErrorMiddleware } from './middleware/error.middleware';
18+
import { serviceAccount } from './models/firebase';
1719

1820
const app = express();
1921
app.set('views', path.join(__dirname, '../views'));
@@ -39,6 +41,11 @@ app.use(
3941

4042
app.use(express.static(path.join(__dirname, 'public'), { maxAge: 31557600000 }));
4143

44+
admin.initializeApp({
45+
credential: admin.credential.cert(serviceAccount),
46+
databaseURL: FIREBASE_DATABASE,
47+
});
48+
4249
// jwt for protected resources
4350
app.use('/top', jwt({ secret: JWT_SECRET, algorithms: ['HS512'] }));
4451
app.use('/authenticate', authenticationController.createToken);

src/controllers/auth.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Request, Response } from 'express';
22
import jwt from 'jsonwebtoken';
3+
import * as admin from 'firebase-admin';
34

45
import logger from '../util/logger';
56
import { ENVIRONMENT, JWT_SECRET } from '../util/secrets';
@@ -24,22 +25,43 @@ export const createToken = (req: Request, res: Response) => {
2425
const [id, hash] = [body.id, body.uniqueHash];
2526

2627
// enable test case user only for development
27-
let users: User[] | undefined;
28+
let testUsers: User[] | undefined = undefined;
29+
let firebaseToken: string | undefined = undefined;
30+
2831
if (ENVIRONMENT === 'development' || process.env.NODE_ENV == 'test') {
29-
users = [testCaseUser];
30-
} else {
31-
// db connection logic or users defined from file from production
32-
users = undefined;
32+
testUsers = [testCaseUser];
33+
} else if (id) {
34+
firebaseToken = id; // verify user with firebase id token,
3335
}
34-
if (users) {
35-
const user = users.filter((user) => user.id === id)[0];
36+
37+
if (testUsers) {
38+
const user = testUsers.filter((user) => user.id === id)[0];
3639
if (user && user.id === id && user.uniqueHash === hash) {
3740
const token = {
3841
token: jwt.sign({ user: 'testCaseUser' }, JWT_SECRET, { algorithm: 'HS512', expiresIn: '30m' }),
3942
};
4043
return res.json(token);
4144
}
45+
} else if (firebaseToken) {
46+
// authen token generated by firebase on client,
47+
// that token is verifiable in firebase admin console
48+
admin
49+
.auth()
50+
.verifyIdToken(firebaseToken)
51+
.then((decodedToken) => {
52+
if (decodedToken) {
53+
console.log('decodedtoken', decodedToken);
54+
const token = {
55+
token: jwt.sign({ user: decodedToken.uid }, JWT_SECRET, { algorithm: 'HS512', expiresIn: '30m' }),
56+
};
57+
return res.json(token);
58+
}
59+
})
60+
.catch((error) => {
61+
logger.debug('firebase admin lib error' + error);
62+
});
4263
}
64+
4365
// if users empty or provided with invalid credentials
4466
return res.status(401).send('Unauthorized');
4567
} catch (e) {

src/models/firebase.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { FIREBASE_SERVICE_ACCOUNT, FIREBASE_DATABASE } from '../util/secrets';
2+
3+
//eslint-disable-next-line @typescript-eslint/no-var-requires
4+
export const serviceAccount = require(FIREBASE_SERVICE_ACCOUNT);
5+
6+
if (FIREBASE_SERVICE_ACCOUNT === '' || FIREBASE_DATABASE === '') {
7+
throw new Error('Please configure env "FIREBASE_DATABASE_URL" & "FIREBASE_SERVICE_ACCOUNT"');
8+
}
9+
10+
// reference
11+
// https://firebase.google.com/docs/auth/users#auth_tokens
12+
13+
// Firebase ID tokens = Created by Firebase when a user signs in to an app. These tokens are signed JWTs that securely
14+
// identify a user in a Firebase project. These tokens contain basic profile information for a user, including the
15+
//user's ID string, which is unique to the Firebase project. Because the integrity of ID tokens can be verified, you
16+
// can send them to a backend server to identify the currently signed-in user.
17+
18+
// Identity provider tokens = Created by federated identity providers, such as Google and Facebook. These tokens
19+
// can have different formats, but are often OAuth 2.0 access tokens. Apps use these tokens to verify that users
20+
// have successfully authenticated with the identity provider, and then convert them into credentials usable by
21+
// Firebase services.
22+
23+
// Firebase custom tokens = Created by your custom auth system to allow users to sign in to an app using your auth
24+
// system. Custom tokens are JWTs signed using a service account's private key. Apps use these tokens much like they
25+
// use the tokens returned from federated identity providers.

src/util/secrets.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export const PORT = process.env['PORT'];
2525
export const RATE_LIMIT_WINDOW_MS = <number>(process.env['RATE_LIMIT_WINDOW_MS'] || 30);
2626
export const RATE_LIMIT_MAX = <number>(process.env['RATE_LIMIT_MAX'] || 2);
2727
export const JWT_SECRET = <string>(process.env['JWT_SECRET'] || '');
28+
export const FIREBASE_DATABASE = <string>(process.env['FIREBASE_DATABASE_URL'] || '');
29+
export const FIREBASE_SERVICE_ACCOUNT = <string>(process.env['FIREBASE_SERVICE_ACCOUNT'] || '');
2830

2931
if (!MONGODB_URI) {
3032
if (prod) {

0 commit comments

Comments
 (0)