0

So, I deployed my frontend on Vercel and my API on Google Cloud Run with docker. It works fine on the development mode however, in production I can't log in with firebase auth.

Logging in with credentials works but its not redirecting the user to the dashboard and logging in with google doesn't creates session at all. On top of that, when I go to dashboard after logging in with credentials, It throws 401 authorization error. And yes, all of my environment variables are correct.

I think that is because something is wrong at my fastify.register(...) section in auth-routes.js page (especially I am suspicious about the sameSite property.) but I can't find what i am doing wrong.

NOTE: All of the problems above are only occurs when I use my app on production (deployed version from vercel). My app works fine on development mode.

auth-routes.js

const admin = require('firebase-admin');
const fp = require('fastify-plugin')
const cookie = require('@fastify/cookie')
const session = require('@fastify/session')
const oauth2 = require('@fastify/oauth2')
async function authPlugin(fastify, options) {
 // Cookie plugin
 fastify.register(cookie)
 
 // Session plugin
 fastify.register(session, {
 cookieName: 'sessionId',
 secret: process.env.SESSION_SECRET,
 cookie: {
 secure: process.env.NODE_ENV === 'production',
 httpOnly: true,
 sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax', 
 maxAge: 7 * 24 * 60 * 60 * 1000,
 path: '/',
 },
 saveUninitialized: false,
 rolling: true
 })
 
 // Login için OAuth2
 fastify.register(oauth2, {
 name: 'googleOAuth2Login',
 scope: ['email', 'profile'],
 credentials: {
 client: {
 id: process.env.GOOGLE_CLIENT_ID,
 secret: process.env.GOOGLE_CLIENT_SECRET
 },
 auth: oauth2.GOOGLE_CONFIGURATION
 },
 startRedirectPath: '/api/login/google',
 callbackUri: `${process.env.API_URL}/api/login/google/callback`,
 cookie: {
 name: 'sessionId',
 secure: process.env.NODE_ENV === 'production',
 httpOnly: true,
 sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax',
 }
 })
 
 // Register için OAuth2
 fastify.register(oauth2, {
 name: 'googleOAuth2Register',
 scope: ['email', 'profile'],
 credentials: {
 client: {
 id: process.env.GOOGLE_CLIENT_ID,
 secret: process.env.GOOGLE_CLIENT_SECRET
 },
 auth: oauth2.GOOGLE_CONFIGURATION
 },
 startRedirectPath: '/api/register/google',
 callbackUri: `${process.env.API_URL}/api/register/google/callback`,
 cookie: {
 name: 'sessionId',
 secure: process.env.NODE_ENV === 'production',
 httpOnly: true,
 sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax',
 }
 })
 }
 
 module.exports = fp(authPlugin, {
 name: 'auth-plugin'
})
async function authRoutes(fastify, options) {
fastify.post('/api/login', async (request, reply) => {
 try {
 const { email, password } = request.body;
 
 if (!email || !password) {
 return reply.code(400).send({
 success: false,
 error: "Email and password are required"
 });
 }
 
 // Firebase Auth REST API ile giriş yap
 const response = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${process.env.FIREBASE_API_KEY}`, {
 method: 'POST',
 headers: {
 'Content-Type': 'application/json'
 },
 body: JSON.stringify({
 email,
 password,
 returnSecureToken: true
 })
 });
 
 const data = await response.json();
 
 if (!response.ok) {
 return reply.code(401).send({
 success: false,
 error: "Invalid credentials"
 });
 }
 
 // Session'a kullanıcı bilgilerini kaydet
 request.session.user = {
 uid: data.localId,
 email: data.email,
 emailVerified: data.emailVerified
 };
 
 // Session'ı kaydet
 await request.session.save();
 
 // reply.send() kullanarak response gönder
 return reply.send({
 success: true,
 token: data.idToken,
 user: {
 uid: data.localId,
 email: data.email,
 emailVerified: data.emailVerified
 }
 });
 
 } catch (error) {
 console.error('Login error:', error);
 return reply.code(500).send({
 success: false,
 error: "Failed to login"
 });
 }
 });
 
 // Register route
 fastify.post('/api/register', async (request, reply) => {
 try {
 const { email, password, name } = request.body;
 
 if (!email || !password) {
 return reply.code(400).send({
 success: false,
 error: "Email and password are required"
 });
 }
 // Firebase Auth'da yeni kullanıcı oluştur
 const userRecord = await admin.auth().createUser({
 email,
 password,
 emailVerified: false,
 displayName: name
 });
 
 // Firestore'da users collection'ında yeni document oluştur
 const userDoc = {
 uid: userRecord.uid,
 email: userRecord.email,
 name: name,
 createdAt: admin.firestore.FieldValue.serverTimestamp(),
 updatedAt: admin.firestore.FieldValue.serverTimestamp(),
 plan: { planType: 'free', planLookupKey: 'free_plan' },
 projects: [],
 credits: 0
 };
 
 await admin.firestore().collection('users').doc(userRecord.uid).set(userDoc);
 
 // Session oluştur
 request.session.user = {
 uid: userRecord.uid,
 email: userRecord.email,
 name: name,
 emailVerified: false
 };
 
 // Session'ı kaydet
 await request.session.save(); 
 const customToken = await admin.auth().createCustomToken(userRecord.uid);
 return reply.send({
 success: true,
 token: customToken,
 user: {
 uid: userRecord.uid,
 email: userRecord.email,
 }
 });
 
 } catch (error) {
 console.error('Register error details:', {
 code: error.code,
 message: error.message,
 stack: error.stack
 });
 
 // Email zaten kullanımda ise
 if (error.code === 'auth/email-already-exists') {
 return reply.code(400).send({
 success: false,
 error: "Email is already in use"
 });
 }
 
 // Zayıf parola
 if (error.code === 'auth/weak-password') {
 return reply.code(400).send({
 success: false,
 error: "Password should be at least 6 characters"
 });
 }
 
 // Diğer Firebase Auth hataları için
 if (error.code?.startsWith('auth/')) {
 return reply.code(400).send({
 success: false,
 error: error.message
 });
 }
 
 return reply.code(500).send({
 success: false,
 error: "Failed to create user",
 details: error.message
 });
 }
 });
 
 // Logout route'u ekle
 fastify.post('/api/logout', async (request, reply) => {
 try {
 // Session'ı temizle
 request.session.destroy();
 
 // Cookie'yi temizle
 reply.clearCookie('sessionId');
 
 return reply.send({
 success: true,
 message: "Logged out successfully"
 });
 } catch (error) {
 console.error('Logout error:', error);
 return reply.code(500).send({
 success: false,
 error: "Failed to logout"
 });
 }
 });
 
 
 // Google login callback route'u
 fastify.get('/api/login/google/callback', async function (request, reply) {
 try {
 const { token } = await this.googleOAuth2Login.getAccessTokenFromAuthorizationCodeFlow(request)
 const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
 headers: {
 Authorization: `Bearer ${token.access_token}`
 }
 })
 const googleUser = await userResponse.json()
 let userRecord
 let isNewUser = false
 try {
 userRecord = await admin.auth().getUserByEmail(googleUser.email)
 } catch (error) {
 if (error.code === 'auth/user-not-found') {
 isNewUser = true
 userRecord = await admin.auth().createUser({
 email: googleUser.email,
 emailVerified: true,
 displayName: googleUser.name
 })
 await admin.firestore().collection('users').doc(userRecord.uid).set({
 uid: userRecord.uid,
 email: googleUser.email,
 name: googleUser.name,
 createdAt: admin.firestore.FieldValue.serverTimestamp(),
 updatedAt: admin.firestore.FieldValue.serverTimestamp(),
 plan: { planType: 'free', planLookupKey: 'free_plan' },
 projects: [],
 credits: 0
 })
 } else {
 throw error
 }
 }
 // Session oluştur
 request.session.user = {
 uid: userRecord.uid,
 email: userRecord.email,
 name: userRecord.displayName || googleUser.name,
 emailVerified: true
 }
 await request.session.save()
 // Custom token oluştur
 const customToken = await admin.auth().createCustomToken(userRecord.uid)
 // Token'ı cookie olarak gönder
 reply.setCookie('auth_token', customToken, {
 path: '/',
 secure: process.env.NODE_ENV === 'production',
 httpOnly: true,
 sameSite: 'lax'
 })
 // Kullanıcıyı dashboard'a yönlendir
 return reply.redirect(`${process.env.APP_URL}/dashboard`);
 } catch (error) {
 console.error('Google auth error:', error)
 return reply.redirect(`${process.env.APP_URL}/auth/error?error=${encodeURIComponent(error.message)}`)
 }
 })
 
 // Google register callback route'u
 fastify.get('/api/register/google/callback', async function (request, reply) {
 try {
 // Google'dan token al
 const { token } = await this.googleOAuth2Register.getAccessTokenFromAuthorizationCodeFlow(request)
 // Google kullanıcı bilgilerini al
 const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
 headers: {
 Authorization: `Bearer ${token.access_token}`
 }
 })
 const googleUser = await userResponse.json()
 
 // Firebase'de kullanıcıyı kontrol et veya oluştur
 let userRecord
 let isNewUser = false
 
 try {
 // Mevcut kullanıcıyı bul
 userRecord = await admin.auth().getUserByEmail(googleUser.email)
 } catch (error) {
 if (error.code === 'auth/user-not-found') {
 isNewUser = true
 // Yeni kullanıcı oluştur
 userRecord = await admin.auth().createUser({
 email: googleUser.email,
 emailVerified: true,
 displayName: googleUser.name
 })
 
 // Firestore'da kullanıcı dokümanı oluştur
 await admin.firestore().collection('users').doc(userRecord.uid).set({
 uid: userRecord.uid,
 email: googleUser.email,
 name: googleUser.name,
 createdAt: admin.firestore.FieldValue.serverTimestamp(),
 updatedAt: admin.firestore.FieldValue.serverTimestamp(),
 plan: { planType: 'free', planLookupKey: 'free_plan' },
 projects: [],
 credits: 0
 })
 } else {
 throw error
 }
 }
 
 // Session oluştur
 request.session.user = {
 uid: userRecord.uid,
 email: userRecord.email,
 name: userRecord.displayName || googleUser.name,
 emailVerified: true
 }
 await request.session.save()
 
 // Custom token oluştur
 const customToken = await admin.auth().createCustomToken(userRecord.uid)
 
 const redirectUrl = `${process.env.APP_URL}/dashboard?token=${customToken}`;
 return reply.redirect(redirectUrl);
 
 } catch (error) {
 console.error('Google auth error:', error)
 return reply.redirect(`${process.env.APP_URL}/auth/error?error=${encodeURIComponent(error.message)}`)
 }
 })
 // Şifre doğrulama route'u
 fastify.post('/api/user/verify-password', async (request, reply) => {
 try {
 // Session kontrolü
 if (!request.session?.user?.uid) {
 return reply.code(400).send({
 success: false,
 error: "Unauthorized"
 });
 }
 const { currentPassword } = request.body;
 const userId = request.session.user.uid;
 // Kullanıcıyı al
 const userRecord = await admin.auth().getUser(userId);
 const userEmail = userRecord.email;
 // Firebase Auth REST API ile şifre doğrulama
 const response = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${process.env.FIREBASE_API_KEY}`, {
 method: 'POST',
 headers: {
 'Content-Type': 'application/json'
 },
 body: JSON.stringify({
 email: userEmail,
 password: currentPassword,
 returnSecureToken: true
 })
 });
 if (!response.ok) { 
 return reply.code(401).send({
 success: false,
 error: "Invalid current password"
 });
 }
 return reply.code(200).send({
 success: true,
 message: "Password verified successfully"
 });
 } catch (error) {
 console.error('Password verification error:', error);
 return reply.code(401).send({
 success: false,
 error: "Failed to verify password"
 });
 }
 });
}
 module.exports = {
 authPlugin,
 authRoutes
 }

server.js

require('dotenv').config()
const fastify = require('fastify')({ 
 logger: true,
 bodyLimit: 2048 * 1024 * 1024 // 2GB limit
})
const cors = require('@fastify/cors')
const { Server } = require('socket.io')
const { authPlugin, authRoutes } = require('./routes/auth-routes')
const captionRoutes = require('./routes/caption-routes')
const imageRoutes = require('./routes/image-routes')
const storageRoutes = require('./routes/storage-routes')
const videoRoutes = require('./routes/video-routes')
const voiceRoutes = require('./routes/voice-routes')
const userRoutes = require('./routes/user-routes')
const paymentRoutes = require('./routes/payments')
// CORS settings
fastify.register(cors, {
 origin: (origin, cb) => {
 // Allow all origins in development environment
 if (!origin || process.env.NODE_ENV === 'development') {
 cb(null, true)
 return
 }
 
 // Allowed origins
 const allowedOrigins = [
 process.env.APP_URL,
 'http://localhost:3000',
 'https://api.stripe.com',
 ]
 
 if (allowedOrigins.indexOf(origin) !== -1) {
 cb(null, true)
 } else {
 cb(new Error('Not allowed by CORS policy'), false)
 }
 },
 credentials: true,
 methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
 allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'Stripe-Signature', 'Cookie']
})
// Remove @fastify/websocket plugin
// fastify.register(require('@fastify/websocket'))
// Create Socket.IO object first and add as decorator
let io = null;
fastify.decorate('io', {
 // Initialize as empty object
 emit: (event, data) => {
 console.log(`Socket.IO not initialized yet. Event: ${event}`, data);
 }
});
// Register all routes
fastify.register(authPlugin)
fastify.register(authRoutes)
fastify.register(captionRoutes)
fastify.register(imageRoutes)
fastify.register(storageRoutes)
fastify.register(videoRoutes)
fastify.register(voiceRoutes)
fastify.register(userRoutes)
fastify.register(paymentRoutes)
// Start server
const start = async () => {
 try {
 await fastify.listen({ port: 8080, host: '0.0.0.0' })
 
 // Connect Socket.IO server to Fastify's HTTP server
 // DISABLE CORS settings for Socket.IO - Use Fastify's CORS settings
 io = new Server(fastify.server, {
 cors: false // Disable CORS
 });
 
 // Listen for Socket.IO connections
 io.on('connection', (socket) => {
 fastify.log.info(`Client connected: ${socket.id}`);
 
 socket.on('disconnect', () => {
 fastify.log.info(`Client disconnected: ${socket.id}`);
 });
 });
 
 // Update decorator (without redefining)
 fastify.io = io;
 
 } catch (err) {
 fastify.log.error(err)
 process.exit(1)
 }
}
start()
// Export Fastify
module.exports = fastify 
asked Apr 30, 2025 at 8:13

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.