I try to use Firebase in my application. The frontend logs the user in using the Web SDK, without any backend. Later, I would like to call some backend APIs. For this reason, I pass the idToken to the backend and try to validate the user as described in the Firebase docs.
When I do the above flow locally using the Firebase Emulator everything works as expected.
When I switch off the Emulator the idToken validation fails with
{
errorInfo: {
code: 'auth/argument-error',
message: 'Firebase ID token has invalid signature. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
},
codePrefix: 'auth'
}
I created a Google hosted Firebase function to check if I can get the idToken validated there. The above setup works when the validation happens within the Google infrastructure.
Based on the above, I think the issue is in my FirebaseApp setup in the API. What that issue might be?
This is my setup.
I define 3 environment variables:
FIREBASE_DB_URL=https://<project-id>.firebaseio.com
FIREBASE_PROJECT_ID=<project-id>
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
I checked and cat $GOOGLE_APPLICATION_CREDENTIALS prints the correct file.
I initialize Firebase in the API with
import admin from "firebase-admin";
if(admin.apps.length == 0) {
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: process.env.FIREBASE_DB_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
});
console.log('Firebase initialized')
} else {
console.warn('Firebase already initialized')
}
and this is the validating code
import { DecodedIdToken } from 'firebase-admin/lib/auth/token-verifier';
import { getAuth } from 'firebase-admin/auth';
import './initializeFirebase';
export default async function needsLoggedInUser(idToken: string): Promise<DecodedIdToken|false> {
try {
return await getAuth().verifyIdToken(idToken)
} catch(err) {
console.error(err)
return false
}
}
I use the above in a NextJS API code as
import { NextApiRequest, NextApiResponse } from 'next'
import { getDatabase } from 'firebase-admin/database';
import 'services/backend/initializeFirebase';
import needsLoggedInUser from 'services/backend/needsLoggedInUser';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// As an admin, the app has access to read and write all data, regardless of Security Rules
const decodedToken = await needsLoggedInUser(req.body.user)
if(!decodedToken) {
return res.status(403).send("403 Forbidden")
}
/// ... rest of the API
}
Related
How to register firebase serviceWorkerRegistration in background.ts file.
I am trying to register firebase in background:
below is code ;
import { getMessaging, onBackgroundMessage } from "firebase/messaging/sw"; // note: we MUST use the sw version of the messaging API and NOT the one from "firebase/messaging"
import { getToken } from "firebase/messaging";
import { initializeApp } from "firebase/app";
const firebase = initializeApp({
// your Firebase config here
});
chrome.runtime.onInstalled.addListener(async () => {
const token = await getToken(getMessaging(), {
serviceWorkerRegistration: self.registration, // note: we use the sw of ourself to register with
});
// Now pass this token to your server and use it to send push notifications to this user
});
onBackgroundMessage(getMessaging(firebase), async (payload) => {
console.log(`Huzzah! A Message.`, payload);
// Note: you will need to open a notification here or the browser will do it for you.. something, something, security
});
The task is to make CRUD operations in Firestore after I make an API call to Cloud Function, which later should trigger a Firestore function to get a set of items in Cards collection.
Here are some rules:
There should be no user authentication needed
It shouldn't need to have a service account with granted permissions
The purpose behind the "rules" is to legitimate operations happening in Cloud Functions as it was an authorized admin itself (Because they are deployed to Firebase safe environment anyways right?). Since the plan is to host the project as a Cloud Function, we should be required to have firebase-admin SDK.
For so far, I tried to implement the same with firebase-functions but it only worked if the rule was not restricted publicly being as:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Cards/{card}{
allow write,read: if false;
}
}
}
Since this works, but the rule is "insecure" I'd like to do these operations only as "admin" would through Cloud Functions. Here is some code that returns an empty array of documents, even though I have data in it viewable from web GUI.
import { getFirestore } from "firebase-admin/firestore";
import * as admin from "firebase-admin";
const GetCard = (cardID: string)=> {
require("../../path-to-the-file.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
getFirestore()
.collection("Cards")
.get()
.then((cardsSnapshot) => {
cardsSnapshot.forEach((card) => {
console.log("card from collection: ", JSON.stringify(card.data()));
});
});
};
EDIT: the reason why I decided to use adminSDK even though Cloud Functions don't need it was the error I was getting:
Error adding document: [FirebaseError: Missing or insufficient permissions.] {
> code: 'permission-denied',
> customData: undefined,
> toString: [Function (anonymous)]
> }
After running this code:
import { initializeApp } from "firebase/app";
import { collection, getDocs, getFirestore } from "firebase/firestore";
const GetCard = (cardID: string): Promise<Card> => {
const firebaseConfig = {...CONFIGS...};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
getDocs(collection(db, "Cards"))
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(`${doc.id} => ${doc.data()}`);
});
})
.catch((e) => {
console.error("Error adding document: ", e);
});
};
... without admin SDK using Firebase Functions
Actually Cloud Functions do use the Admin SDK. As such they totally bypass the Firestore security rules: they have full access rights (write & read) to all the collections of your database.
So if I correctly understand, by using Cloud Functions you will fulfill you needs.
Just to be complete, there is a service account for the Cloud Function but it is automatically set-up by the platform, so it is transparent for you.
I am trying to understand how we can securely call a firebase callable function from firebase https function, Here auth is required so that callable function is not public, it should be accessible only by that https function.
Note: I am new to gcloud and firebase :(
Https Function:
import * as functions from "firebase-functions";
import * as app from "firebase/app";
//import * as auth from "firebase/auth"
import { getFunctions, httpsCallable } from "firebase/functions";
const firebaseConfig = {
apiKey: "WEBAPIKEY",
authDomain: "project.firebaseapp.com",
databaseURL: "https://project.firebaseio.com", // not required though
projectId: "project-id",
storageBucket: "project.appspot.com", // not required
//appId: process.env.APP_ID, // not sure what to provide
messagingSenderId: "1234324" // default service account id
};
const firebaseApp = app.initializeApp(firebaseConfig);
export const caller = functions.https.onRequest((request, response) => {
let messageText = "hi";
const gfunctions = getFunctions(firebaseApp);
const funtionB = httpsCallable(gfunctions, 'funtionB');
funtionB({ text: messageText })
.then((result: any) => {
// Read result of the Cloud Function.
console.log(result);
response.send(result);
});
});
Callable Function:
import * as functions from "firebase-functions";
export const funtionB = functions.https.onCall((data, context) => {
console.log(context.auth); // not getting anything
/* if (!context.auth) { //trying to include this.
return {status: "error", code: 401, message: "Not signed in"};
} */
return new Promise((resolve, reject) => {
resolve({data: "YO", input: data});
});
});
Some logs, which make me to feel bad,
Callable request verification passed {"verifications":{"app":"MISSING","auth":"MISSING"}}
I am not going to user browser to consume this https function, not sure whether we can use auth check without browser. Any way to secure this callable function ? I want to remove alluser access from principal for both the functions to make it private.
I would say this isn't possible because, as you mentioned, the auth checks cannot be done without the browser, also the httpsCallable interface does not allow the context to be forced by passing as a parameter.
I would say that the best option would be to convert your Callable Function into an Http Function where you can implement your own authentication checks, this documentation may be useful for that.
I'm using Azure app Insights for collecting analytical data.I want to handle a case where I'm getting a 400 error due to providing the wrong instrumentation key in the AppInsights.js file. I have created a profile for encrypting instrumentation key in App. Where can I catch this error in code and display fallback UI in a React App.The backend is in Java.
import { ApplicationInsights from'#microsoft/applicationinsightsweb';
import {ReactPlugin,AppInsightsErrorBoundary,} from
'#microsoft/applicationinsights-react-js';
import { createBrowserHistory } from 'history';
import { ClickAnalyticsPlugin } from '#microsoft/applicationinsights-
clickanalytics-js';
import io from 'react/IOGlobal';
const clickPluginInstance = new ClickAnalyticsPlugin();
const browserHistory = createBrowserHistory({ basename: '' });
const reactPlugin = new ReactPlugin();
const clickPluginConfig = {
autoCapture: true,
dataTags: {
useDefaultContentNameOrId: true,
},
};
const appInsights = new ApplicationInsights({
config: { instrumentationKey:
***io.platform.getProfileValue('APP_INSTRUMENTATION_KEY')***,
extensions: [reactPlugin, clickPluginInstance],
extensionConfig: {
[reactPlugin.identifier]: { history: browserHistory },
[clickPluginInstance.identifier]: clickPluginConfig,
},
},
});
appInsights.loadAppInsights();
export default { reactPlugin, appInsights };
Also please help with any other implementation in App Insights to catch the same error.
Thanks In Advance.
Update index.js by putting the following route at the end of the route declaration, before app.listen():
…
// this matches all routes and all methods
app.use((req, res, next) => {
res.status(404).send({
status: 404,
error: ‘Not found’
// You can add your alert
})
})
Check here for more info here
First need to set the Instrumentation key .setup(“Instrumentation key”) To get the instance of the default client that is configured with .setup("Instrumentation key") use appInsights.client. Alternatively, if you want a new client just use getClient("Instrumentation key").
Just like for setup, you can skip providing the Instrumentation key in code if it's in the special instrumentation key environment variable.
Refer for similar kind of issue here
I have created login form with angular 8 and node js. I have set the session using node js in back end. i couldnt check session set or not in angular for avoid access dashboard without logged in. Kindly suggest the way to use login system using angular 8 and node js. Thanks.....
A very popular method is to use JWT (JSON Web Tokens) npm package to authenticate.
The process would be:
Send credentials to the server
Server generates and sends back JWT or a Bearer Token
FrontEnd would store it in browser cookies or localStorage
localStorage.setItem('TOKEN', tokenReceivedFromServer);
In subsequent Api Calls the token would be sent to the server in a Header (Authorization).
Authorization: `JWT ${localStorage.getItem('TOKEN')}`
FYI: JWT keyword is removed from string on the server before parsing token
The frontend can check if the token is set in storage to show login page / dashboard
First we need to check the login credentials valid or not in application.
In angular application component typescript file, we have send the data service in argument, the service send the values to backend using httpclient. If credentials valid we set the value in localstorage.
submitLogin(data:any)
{
this.LoginService.loginData(data).subscribe(data =>{
if(data.body.status_code == 404)
{
Swal.fire({
icon: 'warning',
title: 'Invalid E-Mail/Password!',
}).then(function(){
});
}else if(data.body.status_code ==200)
{
localStorage.setItem("user_id",data.body.token);
this.router.navigate(['/Dashboard']);
}else
{
Swal.fire({
icon: 'error',
title: 'Process Failed!',
}).then(function(){
});
}
});
}
In service.ts file make sure about those packages import
import { HttpClient } from '#angular/common/http';
import { Observable, throwError } from 'rxjs';
import {Login} from './login';
in loginData function
url = "http://localhost:3000/loginCheck";
loginData(Login:Login):Observable<any>
{
return this.http.post(this.url,Login,{observe: 'response'});
}
in backend i have used node.js
in file app.js
first install jsonwebtoken package and include in the file.
npm install jsonwebtoken
then set the jsonwebtoken when where condition satisfies
let payload = {subject:employee_id}
let token = jwt.sign(payload,'secretKey')
var response = {
'token': token,
"status_code":200,
}
res.send(response);
res.end();
Whenever we use the login in angular we must use the authguard routing its helps to access dashboard without logged in.
ng generate guard auth
in auth.guard.ts file we must include the package and service
import { CanActivate, Router } from '#angular/router';
import {LoginService} from './login.service';
export class AuthGuard implements CanActivate {
constructor(private LoginService:LoginService,private router:Router) {}
canActivate(): boolean
{
if(this.LoginService.loggedIn())
{
return true
}else
{
this.router.navigate(['/login']);
return false;
}
}
}
In this file we just checking the localstorage value set or not in boolean datatype.
in service file
add the following code for get and return in boolean type
loggedIn()
{
return !!localStorage.getItem('user_id')
}
getToken()
{
return localStorage.getItem('user_id')
}
if its returns true we can access the dasboard, else its redirected to login page.
We must use this canActive function in routing otherwise it will not working
In app-routing.module.ts file
import { AuthGuard } from './auth.guard';
const routes: Routes = [
{path:'Dashboard',component:DashboardComponent},
{path:'receipt',component:ReciptComponentComponent,canActivate:[AuthGuard]},
];
It will helpus to access dashboard without loggedin but we need to check the token valid or not in backend, we can do that using angular interceptors
we should create the new service with interceptors name
ng g service token-interceptor
In interceptor file we need to import the following
import { Injectable,Injector } from '#angular/core';
import { HttpInterceptor } from '#angular/common/http';
import { LoginService } from './login.service';
In interceptors services inject in different way compared to component.
export class TokenInterceptorService implements HttpInterceptor{
constructor(private Injector:Injector) { }
intercept(req:any,next:any)
{
let loginService = this.Injector.get(LoginService);
let tokenzedReq = req.clone({
setHeaders:
{
Authorization: `Bearer ${loginService.getToken()}`
}
});
return next.handle(tokenzedReq)
}
}
we need to create a function in interceptors with the name intercept, then we need to inject the service as per injector.
In backend we need to create the helper function to verify the jsonwebtoken
if the authorization not set we can send the response 401 not found and can redirected to login page
function verifyToken(req,res,next)
{
if(!req.headers.authorization)
{
return res.status(401).send('Unauthorized request');
}
var token = req.headers.authorization.split(' ')[1];
if(!token)
{
return res.status(401).send('Unauthorized request');
}
if(token === 'null')
{
return res.status(401).send('Unauthorized request');
}
//let payload = jwt.verify(token,'secretKey');
let payload = jwt.decode(token,'secretKey');
if(!payload)
{
return res.status(401).send('Unauthorized request');
}
req.userId = payload.subject;
next();
}
then we can use this middleware function wherever we need
for example
app.get('/dashboard',verifyToken,function(req,res){
let events = [];
res.json(events);
});
In dashboard component ts file
this.dashboardService.getData().subscribe(data=>this.dashboardData=data,
err=>{
if(err instanceof HttpErrorResponse)
{
if(err.status===401)
{
this.router.navigate(['/login']);
}
}
})
in dashboard service ts file
url = "http://localhost:3000/dashboard";
getData()
{
return this.http.get<any>(this.url);
}
in app.module.ts file
import { AuthGuard } from './auth.guard';
import { ReciptComponentComponent } from './recipt-component/recipt-component.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '#angular/common/http';
import { TokenInterceptorService } from './token-interceptor.service';
import { DashboardServiceService } from './dashboard-service.service';
in providers
providers: [AuthGuard,{provide:HTTP_INTERCEPTORS,useClass:TokenInterceptorService,multi:true},DashboardServiceService],