I am getting this error
Access to XMLHttpRequest at 'https://burger-eat-backend.onrender.com/api/v1/me' from origin 'http://localhost:3000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
and my code is
`
import axios from 'axios';
import {server} from "../store"
export const loadUser = ()=\> async(dispatch)=\>{
try {
dispatch({
type:"loadUserRequest",
})
const {data} = await axios.get(`${server}/me`,{
withCredentials: true,
})
console.log(data)
dispatch({
type:"loadUserSuccess",
payload:data.user
})
} catch (error) {
dispatch({
type:"loadUserFailure",
payload:error.response.data.message
})
}
}
import axios from 'axios';
import {server} from "../store"
export const loadUser = ()=\> async(dispatch)=\>{
try {
dispatch({
type:"loadUserRequest",
})
const {data} = await axios.get(`${server}/me`,{
withCredentials: true,
})
console.log(data)
dispatch({
type:"loadUserSuccess",`your text`
payload:data.user
})
} catch (error) {
dispatch({
type:"loadUserFailure",
payload:error.response.data.message
})
}
}
and backend code is
app.use(cors({
credentials:true,
origin:process.env.FRONTEND_URL,
method:["GET", "POST", "PUT", "DELETE"],
optionsSuccessStatus:200
}))
Related
I'm trying to pass throught cors policy using nodejs + express in an heroku api, but getting this error:
Access to XMLHttpRequest at 'https://....' from origin 'https://...' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource
My methods in the frontend are:
const fetchUser = async () => {
const res = await axios.get(
process.env.REACT_APP_API_URL + `/api/user/${userId}`,
{
headers: {
Authorization: `Bearer + ${token}`,
},
},
)
console.log(res.data)
setStatsInit({ waiting: false, stats: res.data })
}
in server:
const corsOptions = {
origin: true,
methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'OPTIONS', 'DELETE'],
headers: [
'Content-Type',
'Authorization',
'application/json',
'text/plain',
'*/*',
],
credentials: true,
maxAge: 3600,
}
app.use(cors(corsOptions))
app.get('/api/user/:uid', middleware.decodeTokenContext, async (req, res) => {
const user = {
uid: req.params.uid,
}
const userStats = await FirestoreClient.getByPath(`users/${user.uid}`)
return res.json({ userStats })
})
The middleware, to check if it is authenticated:
async decodeTokenContext(req, res, next) {
let token = req.headers.authorization.split(' ')[1]
try {
const decodeValue = await admin.auth().verifyIdToken(token)
if (decodeValue) {
return next()
}
return res.json({ message: 'Não autorizado' })
} catch (e) {
return res.json({ message: 'Erro Interno' })
}
}
on the network, on browser, i got this error:
How to solve that error of cors?
i import these two packages (csrf, cookieparser) and using inside the appjs for express only its working and also i tested in postman it's working fine here is my code express js:
const csrf = require('csurf')
const cookieParser = require('cookie-parser')
const csrfProtection = csrf({
cookie: {
httpOnly: true,
maxAge: 3600
}
});
app.use(cookieParser())
app.use(csrfProtection);
app.get('/auth/csrf-token', (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
and also the frontend i using react js and inside the useEffect i fetch the csrf from backend after that i saved in the headers of the axios, but when i send request to the backend, response say invalid csrf :/
useEffect(() => {
const getCsrfToken = async () => {
const { data } = await API.get('/auth/csrf-token');
API.defaults.headers.post['X-CSRF-Token'] = data.csrfToken;
};
getCsrfToken();
}, []);
const handelLogin = (e) => {
e.preventDefault();
API.post('/auth/login', {
headers: {
'Content-Type': 'application/json'
},
data: { email, password },
}).then(({ data }) => {
if (data.token) {
localStorage.setItem('token', data.token);
window.location.href = '/admin'
}
}).catch((e) => {
console.log(e)
})
}
the response from server:
ForbiddenError: invalid csrf token;
As mentioned in https://expressjs.com/en/resources/middleware/csurf.html#using-ajax
Try changing the header property to this,
API.defaults.headers.post['CSRF-Token'] = data.csrfToken;
I solve the problem by adding withcredentials to the axios
all codes after changing
AXIOS.get('get/csrf-token', {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
withCredentials: true
}).then(({ data }) => {
AXIOS.defaults.headers.common['x-csrf-token'] = data.csrfToken
AXIOS.defaults.withCredentials = true
})
I'm stuck with that request already. I'm trying to send FormData to NodeJS server but all I got in backend when I console.log the req.body is empty object. I checked the FormData keys/values and it's all good.
Here is my POST request in frontend:
const createProduct = (e: any) => {
e.preventDefault();
const data = new FormData()
data.append("name", name)
data.append("description", description)
data.append("price", price)
for (const colorAndImage of colorsAndImages) {
data.append('images', colorAndImage.images[1]);
data.append('colors', colorAndImage.colors);
}
data.append("type", type)
for (var pair of data.entries()) {
console.log(pair[0]+ ', ' + pair[1]); // the keys/values are correct
}
fetch('http://localhost:4000/products/create', {
method: 'POST',
body: data
})
.then(response => {
if (response.status === 201) {
setName('')
setDescription('')
setPrice('')
setType('')
} else if (response.status === 500) {
console.log('error');
}
})
.catch(error => console.log(error));
}
And my controller in backend:
productController.post('/create', async (req: Request, res: Response) => {
console.log(req.body)
try {
const data = {
name: req.body.name,
description: req.body.description,
price: req.body.price,
colors: req.body.colors,
images: req.body.images,
type: req.body.type,
likes: req.body.likes
}
let product = await create(data)
res.status(201).json(product)
} catch (error) {
console.log(error);
//res.status(500).json({error: error})
}
})
Even that I obviously send some data, the req.body is an empty object and I got that error:
Error: Product validation failed: name: Path 'name' is required.,
description: Path 'description' is required., price: Path 'price' is
required., type: Path 'type' is required.
at ValidationError.inspect
UPDATE
My express config:
import express, { Application } from 'express';
import cookieParser from 'cookie-parser';
import cors from 'cors';
import auth from '../middlewares/auth';
const corsConfig: cors.CorsOptions = {
credentials: true,
origin: ['http://localhost:3000', 'http://localhost:2000']
}
export default function (app: Application) {
app.use(cors(corsConfig))
app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));
app.use(express.json())
app.use(auth())
}
And root server:
import express, { Application } from "express";
import routes from './routes'
import config from './config/config'
import mongooseConfig from './config/mongoose'
import expressConfig from './config/express'
const app: Application = express()
expressConfig(app);
mongooseConfig();
app.use(express.json())
app.use(routes)
app.listen(config.PORT, () => console.log(`Server is listening on port ${config.PORT}`))
Routes file:
import { Router } from "express";
import authController from "./controllers/authController";
import productController from "./controllers/productController";
const routes = Router()
routes.use('/auth', authController)
routes.use('/products', productController)
export default routes;
Maybe you can just submit it as JSON instead of Form data, this works always :smile:
const createProduct = (e: any) => {
e.preventDefault();
const data = {
"name": name,
"description": description,
"price": price,
"colorsAndImages": colorsAndImages,
"type": type,
};
// Please check mappings as I just transferred what you had :smile:
fetch('http://localhost:4000/products/create', {
method: 'POST',
body: JSON.stringify(data),
})
.then(response => {
if (response.status === 201) {
setName('')
setDescription('')
setPrice('')
setType('')
} else if (response.status === 500) {
console.log('error');
}
})
.catch(error => console.log(error));
}
Server: Node.js, express, Type-Graphql with Apollo Server
In index.ts:
import 'reflect-metadata';
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import { buildSchema } from 'type-graphql';
import { createConnection } from 'typeorm';
import { verify } from 'jsonwebtoken';
import coockieParser from 'cookie-parser';
import cors from 'cors';
import User from './entity/User';
import UserResolver from './resolvers';
import { createAccessToken, createRefreshToken, sendRefreshToken } from './auth';
require('dotenv').config();
const corsOptions = {
allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'X-Access-Token', 'Authorization'],
credentials: true, // this allows to send back (to client) cookies
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
origin: 'http://localhost:3000',
preflightContinue: false,
};
(async () => {
const PORT = process.env.PORT || 4000;
const app = express();
app.use(coockieParser());
app.use(cors(corsOptions));
// -- non graphql endpoints
app.get('/', (_, res) => {
res.send('Starter endpoint');
});
app.post('/refresh_token', async (req, res) => {
const token = req.cookies.jid;
if (!token) {
return res.send({ ok: false, accessToken: '' });
}
let payload: any = null;
try {
payload = verify(token, process.env.REFRESH_TOKEN_SECRET!);
} catch (e) {
console.log(e);
return res.send({ ok: false, accessToken: '' });
}
// token is valid, and the access token can be send back
const user = await User.findOne({ id: payload.userId });
if (!user) {
return res.send({ ok: false, accessToken: '' });
}
if (user.tokenVersion !== payload.tokenVersion) {
return res.send({ ok: false, accessToken: '' });
}
sendRefreshToken(res, createRefreshToken(user));
return res.send({ ok: true, accessToken: createAccessToken(user) });
});
//--
// -- db
await createConnection();
// --
// -- apollo server settings
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [UserResolver],
}),
context: ({ req, res }) => ({ req, res }),
});
await apolloServer.start();
apolloServer.applyMiddleware({
app,
cors: false,
});
// --
app.listen(PORT, () => {
console.log(`Server running on port: ${PORT}`);
});
})();
Login mutation in the UserResolver:
//..
#Mutation(() => LoginResponse)
async login(
#Arg('email') email: string,
#Arg('password') password: string,
#Ctx() { res }: AuthContext,
): Promise<LoginResponse> {
const user = await User.findOne({ where: { email } });
if (!user) {
throw new Error('Incorrect email');
}
const valid = await compare(password, user.password);
if (!valid) {
throw new Error('Incorrect password');
}
sendRefreshToken(res, createRefreshToken(user));
return {
accessToken: createAccessToken(user),
user,
};
}
//..
When handling authentification, the cookies are set in the response header as follows:
//..
export const createAccessToken = (user: User) => sign({ userId: user.id }, process.env.ACCESS_TOKEN_SECRET!, { expiresIn: '10m' });
export const createRefreshToken = (user: User) => sign({ userId: user.id, tokenVersion: user.tokenVersion }, process.env.REFRESH_TOKEN_SECRET!, { expiresIn: '7d' });
export const sendRefreshToken = (res: Response, refreshToken: string) => {
res.cookie('jid', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/refresh_token',
});
};
//..
Client: Next.js, Graphql with URQL
In _app.tsx:
/* eslint-disable react/jsx-props-no-spreading */
import * as React from 'react';
import Head from 'next/head';
import { AppProps } from 'next/app';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { CacheProvider, EmotionCache } from '#emotion/react';
import { createClient, Provider } from 'urql';
import theme from '../styles/theme';
import createEmotionCache from '../lib/createEmotionCache';
import '../styles/globals.css';
// Client-side cache shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
interface IAppProps extends AppProps {
// eslint-disable-next-line react/require-default-props
emotionCache?: EmotionCache;
}
const client = createClient({
url: 'http://localhost:4000/graphql',
fetchOptions: {
credentials: 'include',
},
});
const App = (props: IAppProps) => {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<Provider value={client}>
<CacheProvider value={emotionCache}>
<Head>
<title>Client App</title>
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
</Provider>
);
};
export default App;
Login page does not rely on SSR or SSG (so it is CSR):
import React from 'react';
import LoginForm from '../components/LoginForm/LoginForm';
import Layout from '../layouts/Layout';
interface ILoginProps {}
const Login: React.FC<ILoginProps> = () => (
<Layout
showNavbar={false}
showTransition={false}
maxWidth='xs'
>
<LoginForm />
</Layout>
);
export default Login;
The mutation is used in the LoginForm component to request an access token and set refresh token in the browser cookies:
import React from 'react';
import { useRouter } from 'next/router';
import { useLoginMutation } from '../../generated/graphql';
//...
const LoginForm = () => {
//..
const [, login] = useLoginMutation();
const router = useRouter();
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (disabledSubmit) {
setShowFormHelper(true);
} else {
const res = await login({
email, // from the state of the component
password,
});
if (res && res.data?.login) {
console.log(res.data.login.accessToken);
router.push('/home');
setShowFormHelper(false);
} else {
setHelper('Something went wrong');
}
}
};
//..
};
export default LoginForm;
Issue
So, the problem is that the login response has set-cookie in the header, but the cookie still isn't set in the browser:
Question
Previously, I've implemented the same authentication scheme using the same server code but create-react-app on the client. Everything worked just fine. So, why isn't it working now with next.js? What am I missing?
Work Around
I can use something like cookies-next to put cookies into the storage. The refresh token then would need to be passed in the response data:
import React from 'react';
import { useRouter } from 'next/router';
import { useLoginMutation } from '../../generated/graphql';
import { setCookies } from 'cookies-next';
//...
const LoginForm = () => {
//..
const [, login] = useLoginMutation();
const router = useRouter();
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (disabledSubmit) {
setShowFormHelper(true);
} else {
const res = await login({
email, // from the state of the component
password,
});
if (res && res.data?.login) {
console.log(res.data.login.accessToken);
setCookies('jid', res.data.login.refreshToken);
router.push('/home');
setShowFormHelper(false);
} else {
setHelper('Something went wrong');
}
}
};
//..
};
export default LoginForm;
setCookie accepts options. However, the httpOnly can't be set to true in this case anyway.
Updates
It turns out everything above works in Firefox, but not in Chrome.
in res.cookie defined in the express server, use sameSite:'lax' instead of strict. this may solve the issue.
I have an error :
"Access to fetch at 'http://localhost:4000/' from origin 'http://localhost:3000/' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled."
my server is:
import { GraphQLServer } from 'graphql-yoga'
import { prisma } from './generated/prisma-client'
import resolvers from './resolvers'
const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
context: request => ({
...request,
resolverValidationOptions: { requireResolversForResolveType: false,},
prisma,
}),
opt:{
cors:{
credentials: true,
origin:{['http://localhost:3000/']}
}
}
})
server.start(opt,() => console.log(`Server is running on http://localhost:4000`));
and my client is :
import { ApolloProvider } from 'react-apollo'
import ApolloClient from 'apollo-client'
import {BrowserRouter} from 'react-router-dom'
import { setContext } from 'apollo-link-context'
import { AUTH_TOKEN } from './constants'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory';
import history from './history';
import { onError } from "apollo-link-error";
import { SnackbarProvider, useSnackbar } from 'notistack';
const httpLink = createHttpLink({
uri: 'http://localhost:4000/',
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem(AUTH_TOKEN);
return {
headers: {
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE,OPTIONS',
"Access-Control-Allow-Credentials" : true,
'Access-Control-Allow-Origin': "http://localhost:3000/",
authorization: token ? `Bearer ${token}` : ''
}
}
});
const linkError = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),linkError,
fetchOptions: {
mode: 'cors',
},
});
ReactDOM.render(
<BrowserRouter>
<ApolloProvider client={client}>
<SnackbarProvider>
<App />
</SnackbarProvider>
</ApolloProvider>
</BrowserRouter>,
document.getElementById('root'),
);
Please, help me to find a mistake, I looked at different information, and nothing had helped. Thank you