I'm trying to test an authenticated endpoint in my app. My node app uses express, express session, passport-local, react and next for auth.
I've spent way too many hours trying to solve this problem and could not find a solution.
Basically my test would like to:
send a login request and login
send a request to an authenticated route
receive the appropriate response
My issue was that I had no persistence between the login request and the authenticated route request.
When I sent the login request, passport serializes the user and sets req.user and req._passport.session to the appropriate values.
On the next request - the authenticated route, my middleware looks for req.passport._session or req.user but neither exist. I would get the 401 unauthorized response.
I posted my solution below that took me way too long to figure out.
I solved the persistence issue with Chai HTTP - which uses superagent.
The solution was pretty simple once I had the correct tools. I used the tutorial from the chai-http page but changed it to use async await and try catch.
const { assert } = require('chai');
const chai = require('chai');
const { expect } = require('chai');
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
describe('/authenticatedRequest', () => {
it('should respond appropriately', async () => {
const agent = chai.request.agent('http://localhost:8000');
try {
await agent
.post('/rootPath/loginLocal')
.send({ email: 'email#email.com', password: 'password' });
const authenticatedResponse = await agent.get('/rootPAth/authenticatedRoute');
assert.deepEqual(successResponse, workoutRes);
} catch (e) {
console.log(e);
}
});
});
I now see this post How to authenticate Supertest requests with Passport? - which would have saved me a lot of time.
I hope adding having another post will help someone else with this.
I'm trying to make a request to the express/nodejs backend using nextjs
in pages/reader.js, I have
Reader.getInitialProps = async ({query}) => {
const res = await fetch('http://localhost:3000/api/books/reader/' + query.id);
const json = await res.json();
return {book: json}
};
Unfortunately, that overwrites the cookies stored in the request object on the backend. When I do a console.dir(req.cookies) in the backend node js, express code, I get undefined in book.js where the reader code is.
How can I fetch without overwriting the request object in the express backend?
Look at the example in https://github.com/zeit/next.js/blob/canary/examples/auth0
In the file ssr-profile.js it shows how you can forward your cookies in the request to the server:
// To do fetches to API routes you can pass the cookie coming from the incoming request on to the fetch
// so that a request to the API is done on behalf of the user
// keep in mind that server-side fetches need a full URL, meaning that the full url has to be provided to the application
const cookie = req && req.headers.cookie
const user = await fetchUser(cookie)
I have the backend of a little register/login project on node, which works fine on postman, I'm doing the frontend using just ejs views, the registration works fine and the login alone too, but if I go to the private page, that works with the jwt token, it doesn't find the token I supposedly got when logged in, console says it's undefined.
This is the verification code.
const jwt = require('jsonwebtoken');
module.exports = function (req,res,next){
const token = req.header('auth-token');
console.log(token);
if(!token) return res.status(401).send('access denied');
try {
const verified = jwt.verify(token,process.env.TOKEN_SECRET);
req.user = verified;
//see private content
next();
} catch (err) {
res.status(401).send('invalid token');
}
}
this is the backend of the posts page
const router = require('express').Router();
const verify = require('./verifyToken');
//the verify marks this content as private
router.get('/',verify,(req,res)=>{
res.render('posts.ejs');
});
module.exports = router;
On postman I fill the token name on the headers, but how can I do something like this on the actual thing?
I searched a bit on this and we cannot pass headers to a url.
You can check out this question
Adding http request header to a a href link
When doing an ajax request however we can do attach custom headers and all that. We have full control over the request. I will advise you to use sessions in place of jsonwebtokens. Json Web Tokens are mostly used when using a Front End Framework React, Angular etc because we have to make ajax requests. We than save the token in localStorage and send the token in every subsequent request in the header.
I'm using NextJS as a client side repository to talk to my backend API server (laravel). Auth is done via JWT stored in the cookies. It all works seamlessly when I send an auth request to https://my-backend-server.com/api/login, as I respond with a cookie, and set to the my-backend-server.com domain. And even when I send requests from the browser.
Problems arise when I want to load the page, and sent the request from getInitialProps, as this is a serverside call. How am I able to access the cookies to my-backend-server.com, and put those in the header, so the server-side request from NextJS is properly authorized?
Most of the answers say something about req.cookies or req.headers.cookies, however this is empty as the request in getInitialProps is to http://my-local-clientside-site.com
As you explained correctly, Next's getInitialProps is called on client & on server.
If your Next app & Api services are served from the same domain, your Api service can put the cookie on the domain, and it will work on the client side (browser).
Whenever your Next app accessing the api from server-side, you need to attach the cookie by yourself.
getInitialProps method on the server side gets on the context (first param) the request (as req) from the browser, that means that this request has the cookie.
If you have a custom server, you probably need add to it a cookieParser,
// server.js
import cookieParser from 'cookie-parser';
import express from 'express';
import next from 'next';
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
app.prepare().then(() => {
const nextHandler = app.getRequestHandler();
const server = express();
server.use(cookieParser());
// -----------^
server.get('*', (req, res) => nextHandler(req, res));
});
This parser will parse the Cookie header and put it as an object on the req.
After that, your req.cookie should have the cookie value (make sure that you see that the browser sends it in the document request) you can access it in your getInitialProps,
//pages/index.js
const IndexPage = () => <div>BLA</div>
IndexPage.getInitialProps = (context) => {
if(context.req) {
// it runs on server side
axios.defaults.headers.get.Cookie = context.req.headers.cookie;
}
};
I've given you an example that sets up axios to put the cookie on all requests that will be made from the client.
Felixmosh' answer is half correct. rather than context.req.cookie it should be context.req.headers.cookie.
const IndexPage = () => <div>BLA</div>
IndexPage.getInitialProps = (context) => {
if(context.req) {
// it runs on server side
axios.defaults.headers.get.Cookie = context.req.headers.cookie;
//make api call with axios - it would have correct cookies to authenticate your api call
}
};
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.