After setting a cookie in postman and firing a GET request to my express backend I see the cookie is undefined when I try to log it.
From the browser everything is fine. Out of nowhere this has happened. Any ideas?
I am using cookie parser.
const verifyToken = async (
req: Request,
res: Response,
next: NextFunction
): Promise<void | Response> => {
const { token } = req.cookies;
console.log('first', req.cookies.token);
I'm trying to write and read cookies and falling into a problem below.
This is my basic server side:
server.js
const app = express();
app.use(cors());
app.use(cookieParser());
import routes from '...';
app.use("/foo", routes);
app.listen(8888);
routes.js
const routes = express.Router();
routes.post('/', (req, res) => {
res.cookie("myFoo", "abcd");
res.send("Cookie added");
}
});
routes.get('/', (req, res) => {
res.send(req.cookies.myFoo);
}
});
export default routes;
And my client side at "http://localhost:3000".
I do two HTTP request
POST http://localhost:8888/foo
GET http://localhost:8888/foo
And get the response exactly what I expected abcd. Also, the cookie exists in the browser tab Application > Cookies too.
The problem cases when axios is used in the client.
const api = axios.create({
baseURL: "http://localhost:8888/foo"
});
async function setCookie(object) {
return api.post("/", object)
.then((res) => {
return res;
});
}
function getCookie() {
return api.get("/")
.then((res) => {
return res;
});
}
setCookie({})
.then((res) => {
getCookie();
})
The api.post() run usually and the header response Set-Cookie is correct. But cookies in the browser tab Application > Cookies are empty. Also, api.get() get the undefined.
I did try to move res.cookie() or the set cookie job in server side to GET route it WORKS on both HTTP and axios
routes.get('/', (req, res) => {
res.cookie("myFoo", "abcd");
});
tldr: Set cookie in HTTP POST method work fine but when client use axios to call so it causes problems.
Can you show me why this happened? And which code part went wrong that caused me into this?
Cookies are only used in cross-origin Ajax requests when:
The client asks to use them
The server grants permission to use them cross origin
So you need to change the client side code to ask for them:
const api = axios.create({
baseURL: 'http://localhost:8888/',
withCredentials: true,
});
And the server code to grant permission (note that you can't use credentials at the same time as the wildcard for origins).
app.use(cors({
origin: 'http://localhost:3000',
credentials: true
}));
I'm trying to set up a React/Redux - NodeJs Express stack with Google OAuth authentication. My issue is a CORs error kicking back in the console. I've found some Stack Overflow questions that I feel were exactly my issue, but the solutions aren't producing any results. Specifically these two: CORS with google oauth and CORS/CORB issue with React/Node/Express and google OAuth.
So I've tried a variety of fixes that all seem to lead me back to the same error. Here's the most straight forward of them:
const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 200,
credentials: true
}
app.use(cors(corsOptions));
This is in the root of my API.js file. The console error I receive state:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fapi%2Foauth%2Fgoogle%2Freturn&scope=profile&client_id=PRIVATE_CLIENT_ID.apps.googleusercontent.com' (redirected from 'http://localhost:5000/api/oauth/google') from origin 'null' 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.
So if I look at my network log in the dev tools, I look at my request to the API path and see what I expect to see:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Origin: http://localhost:3000
So it seems to me that my issue isn't within my front to back communication. Which leads me to believe it's maybe an issue with the Passport token validation. Here are my simplified routes:
router.post('/oauth/google', passport.authenticate('googleVerification', {
scope: ['profile']
}), (req, res) => {
console.log('Passport has verified the oauth token...');
res.status(200)
});
And the callback route:
router.get('/oauth/google/return', (req, res) => {
console.log('google oauth return has been reached...')
res.status(200)
});
And lastly, the simplified strategy:
passport.use('googleVerification', new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_SECRET,
callbackURL: 'http://localhost:5000/api/oauth/google/return'
}, (accessToken, refreshToken, profile, cb) => {
console.log('Passport OAuth Strategy reached');
cb(null, profile)
}));
I know all these won't lead to anything functional, but I've just ripped out as much fluff as I can trying to get a handle on where the block in my authentication flow is. Just in case it may be helpful in narrowing this down, here is the action creator in Redux that logs the last step in the process before the errors start coming ('redux accepting token and passing to API:', token):
export const signIn = (token) => {
console.log('redux accepting token and passing to API:', token)
return async dispatch => {
const res = await Axios({
method: 'post',
url: `${API_ROOT}/api/oauth/google`,
withCredentials: true,
data: {
access_token: token
}
})
console.log('API has returned a response to redux:', res)
dispatch({
type: SIGN_IN,
payload: res
})
}
};
This never actually reaches the return and does not log the second console.log for the record.
That CORS is not related to making request to google because when you registered your app in console.developers.google.com it is already handled by google.
The issue is between CRA developer server and express api server. You are making request from localhost:3000 to localhost:5000. To fix this use proxy.
In the client side directory:
npm i http-proxy-middleware --save
Create setupProxy.js file in client/src. No need to import this anywhere. create-react-app will look for this directory
Add your proxies to this file:
module.exports = function(app) {
app.use(proxy("/auth/google", { target: "http://localhost:5000" }));
app.use(proxy("/api/**", { target: "http://localhost:5000" }));
};
We are saying that make a proxy and if anyone tries to visit the route /api or /auth/google on our react server, automatically forward the request on to localhost:5000.
Here is a link for more details:
https://create-react-app.dev/docs/proxying-api-requests-in-development/
by default password.js does not allow proxied requests.
passport.use('googleVerification', new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_SECRET,
callbackURL: 'http://localhost:5000/api/oauth/google/return',
proxy:true
}
One important thing here is, you should understand why proxy is used. As far as I understood from your code, from browser, you make request to express, and express will handle the authentication with password.js. After password.js runs through all authentication steps, it will create a cookie, stuffed it with the id, give it to express and express will send it to the browser. this is your app structure:
BROWSER ==> EXPRESS ==> GOOGLE-SERVER
Browsers automatically attaches the cookie to the evey request to server which issued the cookie. So browser knows which cookie belongs to which server, so when they make a new request to that server they attach it. But in your app structure, browser is not talking to GOOGLE-SERVER. If you did not use proxy, you would get the cookie from GOOGLE-SERVER through express, but since you are not making request to GOOGLE-SERVER, cookie would not be used, it wont be automatically attached. that is the point of using cookies, browsers automatically attaches the cookie. BY setting up proxy, now browser is not aware of GOOGLE-SERVER. as far as it knows, it is making request to express server. so every time browser make request to express with the same port, it attaches the cookie. i hope this part is clear.
Now react is communicating only with express-server.
BROWSER ==> EXPRESS
since react and exress are not on the same port, you would get cors error.
there are 2 solutions. 1 is using the cors package.
its setup is very easy
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors()) // use this before route handlers
second solution is manually setting up a middleware before route handlers
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Methods",
"OPTIONS, GET, POST, PUT, PATCH, DELETE"
);
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
next(); // dont forget this
});
You can't make an axios call to the /oauth/google route!
Here's my solution... Code is slightly different but you will get the concept.
// step 1:
// onClick handler function of the button should use window.open instead
// of axios or fetch
const loginHandler = () => window.open("http://[server:port]/auth/google", "_self")
//step 2:
// on the server's redirect route add this successRedirect object with correct url.
// Remember! it's your clients root url!!!
router.get(
'/google/redirect',
passport.authenticate('google',{
successRedirect: "[your CLIENT root url/ example: http://localhost:3000]"
})
)
// step 3:
// create a new server route that will send back the user info when called after the authentication
// is completed. you can use a custom authenticate middleware to make sure that user has indeed
// been authenticated
router.get('/getUser',authenticated, (req, res)=> res.send(req.user))
// here is an example of a custom authenticate express middleware
const authenticated = (req,res,next)=>{
const customError = new Error('you are not logged in');
customError.statusCode = 401;
(!req.user) ? next(customError) : next()
}
// step 4:
// on your client's app.js component make the axios or fetch call to get the user from the
// route that you have just created. This bit could be done many different ways... your call.
const [user, setUser] = useState()
useEffect(() => {
axios.get('http://[server:port]/getUser',{withCredentials : true})
.then(response => response.data && setUser(response.data) )
},[])
Explanation....
step 1 will load your servers auth url on your browser and make the auth request.
step 2 then reload the client url on the browser when the authentication is
complete.
step 3 makes an api endpoint available to collect user info to update the react state
step 4 makes a call to the endpoint, fetches data and updates the users state.
When logging in to my Yii2 + angularjs page, I create cookie with user data:
$jwt = JWT::encode(array(1,2,3), 'myKey123')
setcookie('myData', $jwt, time() + 3600);
I want to access it in my nodejs + express app - before processing request I have to check, if user didn't change 'myData' cookie. This is how I do it now:
app.use(cookieParser());
app.get('/*', function (req, res, next) {
if(req.cookies.myData) {
jwt.verify(req.cookies.myData, 'myKey123', function(err, decoded) {
if(err)
res.sendStatus(403);
else
return next();
});
} else {
res.sendStatus(403);
}
});
If after I logging in I call expressjs route directly in browser, app sees cookie.
Problem: If route is called by making $http.get() request, expressjs app doesn't see any cookies.
Yii2 and expressjs runs on the same IP, but on different ports, but I've read, that different ports shouldn't be the reason, should it? I've played around with setting different cookie parameters, but nothing seems to help. I'd appreciate any help or hints I could get, thank you!
So I know what's wrong now - in my $http.get() requests I simply didn't set withCredentials: true. When request was "made" directly in browser, cookies were passed by default, but when doing it via $http.get(), I had to set extra argument, for example:
$http.get(myService('myRoute'), {
params: {
param1: 1,
param2: 2
},
withCredentials: true
}).then(function(response) {
$scope.returnedData = response.data;
});
Source: AngularJs documentation for $http requests
withCredentials - {boolean} - whether to set the withCredentials flag on the XHR object. See requests with credentials for more information.
I use expressJS as my NodeJS server. The user sends me his login info through a POST and after checking the credentials I render a page:
router.post("/login", function (req: Request, res: Response, next) {
if(credentialsOK){
res.render('main');
}
});
The problem is that the URL becomes http://myaddress/login and I would like to remove the /login of the address. I don't want to use redirect as I want to send local variables through the render.
How can I change the URL?
You can still pass your local variables through res.redirect.
router.post("/login", function (req: Request, res: Response, next) {
if(credentialsOK){
req.session.localVar = yourLocalVar;
res.redirect('/main');
}
})
Then in main router:
router.get("/main", function (req: Request, res: Response, next) {
var yourLocalVar = req.session.localVar;
res.render('main');
})
You cannot change the URL from the server side, but you can change the URL
by using the javascript method window.history.pushState("", "", '/');
<script>
window.history.pushState("", "", '/');
</script>