Replies: 1 comment
-
these are the browser errors i am getting 1. in the networks tab 2. in the console tab: Google OAuth Settings in cloud: |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
i am trying to build a platform where the next.js frontend is being hosted on port 3000 and the express.js backend is being hosted on port 5000 (basically both are completely decoupled and can talk to each other only via REST APIs).
You find the code for backend and frontend that i currently have.
The frontend has to request the backend for authentication on routes starting at "/api/auth/*".
The flow i am trying to build:
How do i build this without having any CORS issues? i have tried to make this work for days but can't seem to be getting it worked.
(NOTE: I tried to hit the sign in url for google oauth on backend using postman and that seems to be working as it returned a google sign-in page as a response)
Backend: Express.js server with Auth.js:
import express from 'express';
import ExpressAuth from "@/auth";
import cors from 'cors';
import manageRouter from '@/routes/manage';
import { errorHandler } from '@/middlewares/error.middleware';
import { currentSession } from '@/middlewares/auth.middleware';
const FRONTEND_URL = 'http://localhost:3000'
const app = express();
// for preflight requests
app.options('*', cors({
origin: FRONTEND_URL,
credentials: true,
}));
// for CORS
app.use(cors({
origin: FRONTEND_URL, // Your frontend URL
credentials: true, // Required for cookies, authorization headers with HTTPS
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
allowedHeaders: ['Content-Type', 'Authorization'], // Explicitly allow headers
}));
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
//debugging
app.use((req, res, next) => {
console.log(
Received request: ${req.method} ${req.url}
)console.log('Request Headers:', req.headers);
console.log('Request Method:', req.method);
// console.log(req.body)
next()
})
// Set session in res.locals
app.use(currentSession)
// API Routes
app.use("/api/auth/*", ExpressAuth)
app.use('/api/manage', manageRouter);
// Error Handler Middleware
app.use(errorHandler);
export default app;
Frontend: Next.js (using custom session provider):
"use client"
import * as React from "react"
// Define the backend URL (can be configured via environment variables)
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:5000"
// const APP_URL = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"
// Define the session context
const SessionContext = React.createContext(undefined)
// Custom hook to use the session
export function useSession() {
const context = React.useContext(SessionContext)
if (!context) {
throw new Error("useSession must be used within a SessionProvider")
}
return context
}
// Session provider component
export function SessionProvider({ children }) {
const [session, setSession] = React.useState(null)
const [loading, setLoading] = React.useState(true)
// Fetch the session from the backend
const fetchSession = async () => {
try {
const response = await fetch(
${BACKEND_URL}/api/auth/session
, {credentials: "include", // Include cookies for cross-origin requests
})
if (!response.ok) {
throw new Error("Failed to fetch session")
}
const data = await response.json()
setSession(data)
} catch (error) {
console.error("Failed to fetch session:", error)
setSession(null)
} finally {
setLoading(false)
}
}
// Update the session
const updateSession = async (data) => {
try {
const response = await fetch(
${BACKEND_URL}/api/auth/session
, {method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
credentials: "include", // Include cookies for cross-origin requests
})
if (!response.ok) {
throw new Error("Failed to update session")
}
const newSession = await response.json()
setSession(newSession)
return newSession
} catch (error) {
console.error("Failed to update session:", error)
return null
}
}
// Sign in
const signIn = async (provider, options = {}) => {
const { callbackUrl = window.location.href } = options
try {
const response = await fetch(
${BACKEND_URL}/api/auth/signin/${provider}?${new URLSearchParams({callbackUrl})}
, {method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ callbackUrl}),
credentials: "include", // Include cookies for cross-origin requests
})
if (!response.ok) {
throw new Error("Failed to sign in")
}
const data = await response.json()
if (data.url) {
window.location.href = data.url
}
return data;
} catch (error) {
console.error("Failed to sign in:", error)
return null
}
}
// Sign out
const signOut = async (options = {}) => {
const { callbackUrl = window.location.href } = options
try {
const response = await fetch(
${BACKEND_URL}/api/auth/signout
, {method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ callbackUrl }),
credentials: "include", // Include cookies for cross-origin requests
})
if (!response.ok) {
throw new Error("Failed to sign out")
}
const data = await response.json()
if (data.url) {
window.location.href = data.url
}
setSession(null)
return data
} catch (error) {
console.error("Failed to sign out:", error)
return null
}
}
// Initialize session on mount
React.useEffect(() => {
fetchSession()
}, [])
// Provide the session context
const value = {
session,
loading,
update: updateSession,
signIn,
signOut,
}
return (
<SessionContext.Provider value={value}>{children}</SessionContext.Provider>
)
}
Frontend: SignIn Button to initiate the flow
"use client";
import { FcGoogle } from "react-icons/fc";
import { Button } from "@/components/ui/button";
import { DEFAULT_LOGIN_REDIRECT } from "@/routes";
import { useSearchParams } from "next/navigation";
import { useSession } from "@/components/auth/session-provider";
const APP_URL = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"
export const Social = () => {
// const {signin} = useAuth();
const {signIn} = useSession();
const searchParams = useSearchParams();
const callbackUrl = searchParams?.get("callbackUrl");
const onClick = (Provider: "google") => {
signIn(Provider, {
callbackUrl: callbackUrl || (APP_URL + DEFAULT_LOGIN_REDIRECT),
});
// signIn(Provider)
};
return (
<Button
variant="outline"
className="w-full"
onClick={() => onClick("google")}
>
Sign In with Google
);
};
I am skipping the CSRF Token check using the skipCSRFCheck flag temporarily
This is my current config:
export default {
adapter: PrismaAdapter(db),
session: {
strategy: "database",
maxAge: 30 * 24 * 60 * 60, // 30 days - expiry time
updateAge: 24 * 60 * 60, // 24 hours - update expiry time every 24 hours if active (adds maxAge from time of update)
// maxAge: 60, // 5m
// updateAge: 30, // 30s
},
trustHost: true,
secret: process.env.AUTH_SECRET,
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
scope: scopes.join(" "),
access_type: "offline",
prompt: "consent",
},
},
openId: true,
}),
],
} satisfies ExpressAuthConfig;
Custom redirect callback for Auth config:
async redirect({ url, baseUrl }) {
// Redirect back to frontend after successful auth
console.log("redirect callback executing for url: "+url+", baseurl: "+baseUrl);
return url ? url : baseUrl
},
Beta Was this translation helpful? Give feedback.
All reactions