Reputation: 85
I have a React frontend making a call to a NextJS endpoint. I have what I believe are the correct headers set on the server using the next.config.js
file...
module.exports = {
reactStrictMode: true,
webpack: (config, options) => {
if (!options.isServer) {
config.resolve.fallback.fs = false
}
return config
},
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'origins', value: '*' },
{ key: 'Bypass-Tunnel-Reminder', value: '*' },
{ key: 'Access-Control-Allow-Origin', value: '*' },
{ key: 'Access-Control-Request-Methods', value: 'POST, GET, OPTIONS' },
{ key: 'Access-Control-Allow-Headers', value: 'Authorization, Content-Type' },
],
},
]
},
}
Nevertheless, I keep getting a CORS error when I call the endpoint from my frontend.
Here is the frontend code...
axios.post(
'http://localhost:3001/github/setCredentials',
{
accessCode,
},
{
headers: {
Authorization: localStorage.getItem('jwt_token'),
'Content-Type': 'application/x-www-form-urlencoded',
},
},
)
The jwt_token
looks like "Bearer <TOKEN>"
The endpoint looks like this...
const authenticate = (method, req, res) =>
new Promise((resolve, reject) => {
passport.authenticate(method, { session: false }, (error, token) => {
if (error) {
reject(error)
} else {
resolve(token)
}
})(req, res)
})
passport.use(jwtStrategy)
const handler = nextConnect({
onError: (err, req: NextApiRequest, res: NextApiResponse, next) => {
console.error(err.stack)
res.status(500).end('Something broke!')
},
onNoMatch: (req: NextApiRequest, res: NextApiResponse) => {
res.status(404).end('Page is not found')
},
})
.use(passport.initialize())
.post(async (req: NextApiRequest, res: NextApiResponse) => {
try {
const user = await authenticate('jwt', req, res)
if (!user) return res.status(403).json({ error: 'Unauthenticated user' })
return res.status(200).json({ success: true })
} catch (err) {
return res.status(500).json({ error: err.toString() })
}
})
export default handler
The passport.js authentication strategy looks like this...
const opts: any = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken()
opts.secretOrKey = JWT_SECRET
export const jwtStrategy = new JwtStrategy(opts, function (jwt_payload, done) {
mongoose
.getUserByEthAddress(jwt_payload.address)
.then((user: User | null) => {
return done(null, user)
})
.catch((err) => {
return done(err, false)
})
})
Interestingly (frustratingly?) enough, hitting my other endpoints do not cause CORS errors. Also, when I remove the "Authorization" header from the request, I get a typical 403 error thrown by the middleware being unable to find the user, but no CORS error.
Also, when I make this call on Postman with the Authorization token and everything, it works great, so the auth layer is not the issue.
Any ideas what could be causing this? Everything I read makes it seem like setting the "Access-Control..." headers in the next.config.js
file would solve this, but that has not been the case.
Upvotes: 1
Views: 3140
Reputation: 11
I configured my next.js middleware to handle accepted origins and return website or route only if origin type is accepted for production mode. For local development I set origin value as *
because no need to handle CORS for localhost. You can copy paste this code to your middleware.ts
file.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const isProduction = process.env.DATASET === 'production';
const corsOrigins = isProduction
? [
'domain1',
'domain2',
'domain3',
'domain4',
]
: ['*'];
export function middleware(req: NextRequest) {
const origin = req.headers.get('origin');
const res = NextResponse.next();
// Set CORS headers
res.headers.set('Access-Control-Allow-Credentials', 'true');
res.headers.set('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT');
res.headers.set(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
);
if (corsOrigins.includes('*') || corsOrigins.includes(origin || '')) {
res.headers.set('Access-Control-Allow-Origin', origin || '*');
}
return res;
}
export const config = {
matcher: '/api/:path*', // Match all API routes
};
Please modify the corsOrigins
array value (add all your needed domains).
Upvotes: 0
Reputation: 85
@jub0bs led me to the fix. I needed to do a couple things.
.use(async (req: NextApiRequest, res: NextApiResponse, next) => {
await NextCors(req, res, {
// Options
methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
origin: '*',
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
})
next()
})
.options((req: NextApiRequest, res: NextApiResponse) => {
return res.status(200).send('ok')
})
Then, my passport middleware populated the request with a req.user
object that I can use when I handle the POST method.
Upvotes: 1