The backend is configured with Node.js and the front end uses Vite. We distributed what we were developing locally to Firebase Functions.
I thought there would be no problem because I used a Google Cloud Service account in the existing local area, but the API using Google Analytics alone causes 500 errors.
The following errors occur on the server terminal.
> Error during visitor stats fetch: Error: 7 PERMISSION_DENIED: Request had insufficient authentication scopes.
> at callErrorFromStatus (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/call.js:31:19)
> at Object.onReceiveStatus (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/client.js:193:76)
> at Object.onReceiveStatus (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:360:141)
> at Object.onReceiveStatus (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:323:181)
> at /Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/resolving-call.js:129:78
> at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
> for call at
> at ServiceClientImpl.makeUnaryRequest (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/client.js:161:32)
> at ServiceClientImpl.<anonymous> (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/make-client.js:105:19)
> at /Users/user/Documents/dev/works/project-backend/functions/node_modules/@google-analytics/data/build/src/v1beta/beta_analytics_data_client.js:233:29
> at /Users/user/Documents/dev/works/project-backend/functions/node_modules/google-gax/build/src/normalCalls/timeout.js:44:16
> at OngoingCallPromise.call (/Users/user/Documents/dev/works/project-backend/functions/node_modules/google-gax/build/src/call.js:67:27)
> at NormalApiCaller.call (/Users/user/Documents/dev/works/project-backend/functions/node_modules/google-gax/build/src/normalCalls/normalApiCaller.js:34:19)
> at /Users/user/Documents/dev/works/project-backend/functions/node_modules/google-gax/build/src/createApiCall.js:112:30
> at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
> code: 7,
> details: 'Request had insufficient authentication scopes.',
> metadata: Metadata {
> internalRepr: Map(4) {
> 'endpoint-load-metrics-bin' => [Array],
> 'grpc-server-stats-bin' => [Array],
> 'google.rpc.errorinfo-bin' => [Array],
> 'grpc-status-details-bin' => [Array]
> },
> options: {}
> },
> statusDetails: [
> ErrorInfo {
> metadata: [Object],
> reason: 'ACCESS_TOKEN_SCOPE_INSUFFICIENT',
> domain: 'googleapis.com'
> }
> ],
> reason: 'ACCESS_TOKEN_SCOPE_INSUFFICIENT',
> domain: 'googleapis.com',
> errorInfoMetadata: {
> method: 'google.analytics.data.v1beta.BetaAnalyticsData.RunReport',
> service: 'analyticsdata.googleapis.com'
> }
> }
When I developed it local, I didn't have any rights issues, so I thought there would be no errors when I deployed it to Firebase Functions.
As you can see in the picture, only visor-stats (APIs using Google Analytics) are experiencing 500 errors.
I didn't know the problem, so I added the authority.
I am using the service account and downloaded it in json format, and I entered the file path correctly in the .env file.
Is there a new permission setting that I need to do?
Of course, Google Analytics API and Google Analytics Data API are in use.
I'd appreciate it if you could help me. ㅠㅠ
I am conducting a local test with the command below.
firebase emulators:start --only functions
The functions/index.js code is as follows.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const dotenv = require('dotenv');
const app = require('./app');
// 환경 변수 설정
dotenv.config();
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
}
exports.api = functions.https.onRequest(app);
functions/app.js
const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const swaggerSetup = require('./src/config/swagger');
const dotenv = require('dotenv');
const requestRoutes = require('./src/routes/requestRoutes');
const cors = require('cors');
dotenv.config();
const authRoutes = require('./src/routes/authRoutes');
const blogRoutes = require('./src/routes/blogRoutes');
const projectRequestRoutes = require('./src/routes/projectReqRoutes');
const visitorStatsRoutes = require('./src/routes/visitorStatsRoutes');
const bakRoutes = require('./src/routes/bakRoutes');
const authController = require('./src/controllers/authController');
const app = express();
// CORS 설정
app.use(cors({
origin: ['http://localhost:5173', 'https://yinvl-web.web.app'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // 인증 정보(쿠키 등) 포함 여부
}));
app.options('*', cors()); // Preflight 요청을 위한 CORS 설정
// Morgan 로깅 구성
const morganFormat = process.env.NODE_ENV === 'production' ? 'combined' : 'dev';
app.use(morgan(morganFormat));
// Express 설정: JSON 및 URL-encoded 페이로드 처리
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Cookie Parser
app.use(cookieParser(process.env.COOKIE_SECRET));
// Session configuration
app.use(session({
secret: process.env.COOKIE_SECRET, // 세션 암호화 키
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true, // javascript에서 접근 못하도록 (보안에 좋음)
secure: true
}
}));
// Initialize Passport
app.use(authController.initializePassport);
app.use(authController.session);
// Routes
app.use('/', authRoutes); // 로그인 및 인증 라우트
app.use('/project-requests', projectRequestRoutes); // 고객의 프로젝트 요청 폼
app.use('/yinvl/blog', blogRoutes); // 블로그 관련 라우트
app.use('/yinvl', requestRoutes); // 어드민 대시보드에서 요청 조회
app.use('/yinvl', visitorStatsRoutes); // 방문자 통계 관련 라우트
app.use('/yinvl', bakRoutes); // 백업 관련 라우트
// Swagger API 문서화 설정
swaggerSetup(app);
module.exports = app;