I am trying to set up a simple register/login form with vuejs in the front and a server with express js using the passport library to setup a local and social media startegy.
But I can't seem to pass cookies to the front end when I login with the local strategy.
Also when I sign in with google I get the cookie on the front end but it is not sent with the next API call but this is a subject for another question.
I was confused by this so I made a simple project just to receive and send cookies and it works. Here is the back end:
//headers in app.js
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:8080');
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
// index file
router.get('/cookie', function (req, res, next) {
res.cookie("token", "mytoken");
res.send("cookie sent");
});
router.get('/info', function (req, res, next) {
cookies = req.cookies;
console.log(cookies);
res.cookie("token", "mytoken");
res.send("cookie sent");
});
And here is my front end methods that call the API:
methods: {
async getCookie() {
await axios.get("http://localhost:3000/cookie",{withCredentials:true}).then((response) => {
console.log(response);
}).catch((e) => {
console.log(e);
});
},
async sendCookie() {
await axios.get("http://localhost:3000/info",{withCredentials:true}).then((response) => {
console.log(response);
}).catch((e) => {
console.log(e);
});
}
}
With that I have no problem passing the cookie in the requests and receiving it.
Now on my real project I have this on the back end
//Headers just like the other project
router.post('/users/login', function (req, res, next) {
passport.authenticate('local', { session: false }, function (err, user, info) {
if (err) { return next(err); }
if (user) {
res.cookie('token', 'mytoken');
return res.json({ user: user.toAuthJSON() });
} else {
return res.status(401).json(info);
}
})(req, res, next);
});
Front end call:
// Service file to call the api
axios.defaults.baseURL = "http://127.0.0.1:3000/api/";
axios.defaults.withCredentials = true;
const ApiService = {
get(resource, slug = "") {
return axios.get(`${resource}/${slug}`).catch(error => {
throw new Error(`ApiService ${error}`);
});
},
...
}
export default ApiService;
//actual call in authetification module file
[LOGIN](context, credentials) {
return new Promise(resolve => {
ApiService.post("users/login", { email: credentials.email, password: credentials.password })
.then(({ data }) => {
context.commit(SET_AUTH, data.user);
resolve(data);
})
.catch(({ response }) => {
context.commit(SET_ERROR, response.data.errors);
});
});
},
//
The request works but the cookie is blocked by Chrome:
I don't see what is the difference in my two projects that would trigger this warning on Chrome for the last one.
EDIT: In my original post axios.defaults.baseURL was not set to my actual value.
After last update of chrome browser I also started to get this error. A solution to solve this go to chrome://flags and disable SameSite by default cookies and disable Enable removing SameSite=None cookies. This will solve your problem. I think another way to solve this changing your cookie settings(samesite attribute) when you are creating your session.
Find the solution thank to #skirtle. I set axios.defaults.baseURL to "http://127.0.0.1:3000/api/" but localhost and 127.0.0.1 are not interchangeable and considered two different domains. I switched to axios.defaults.baseURL = "http://localhost:3000/api" and it fixed the problem.
Related
I am trying to redirect the web page back to the original requested page after it hits the middleware which checks for authentication using passport.js. I understand that the following code works for the passport.js version but the updated version causes a new req.session.id to be created causing the req.session.returnTo to be undefined.
I am using express.js (version 4.18.2) as my web framework and have downloaded the latest passport (version 0.6.0).
my middleware:
module.exports.isLoggedIn = (req, res, next) => {
if (!req.isAuthenticated()) {
req.session.save(function (err) {
if (err) {
return next(err);
}
})
req.session.returnTo = req.originalUrl
redirectUrl = req.session.returnTo
req.flash('error', 'you must be signed in');
return res.redirect('/login')
}
next();
}
my router code:
router.post('/login', passport.authenticate('local', { failureFlash: true, failureRedirect: '/login' }), (req, res) => {
req.flash('success', 'Welcome back!');
req.session.reload(function (err) {
if (err) {
return next(err);
}
})
const redirectUrl = req.session.returnTo;
delete req.session.returnTo;
return res.redirect(redirectUrl);
})
I have tried using req.session.save to save the session id and then reload using req.session.reload later on hoping that I can access req.session.returnTo but have no luck. If I set my redirectUrl to originalUrl, it redirects me to the /login route. Is there a req.previousUrl or similar that could be used? I tried searching but no luck!
I have also tried entering the following in my passport.authenticate (as suggested by others) but does not work for me.
keepSessionInfo: true
When the user logins, the access token is created and sent to the user, it is then stored in sessionStorage. Everything before this works fine. My problem is that I do not know how to use the access token to gain access to protected routes.
express app.js (smoothies is the protected route)
app.get('/smoothies', requireAuth, (req, res) => res.render('smoothies'));
authMiddleware.js
const User = require('../models/User');
const requireAuth = (req, res, next) => {
const authHeader = req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1]
// check json web token exists & is verified
if (token) {
jwt.verify(token, 'night of fire', (err, decodedToken) => {
if (err) {
console.log(err.message);
res.redirect('/login?err=auth');
} else {
console.log(decodedToken);
next();
}
});
} else {
res.redirect('/login?err=auth');
}
};
// check current user
module.exports = { requireAuth };
smoothies.ejs
var myHeaders = new Headers();
myHeaders.append("Authorization", `Bearer ${token}`);
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
fetch("http://localhost:3000/smoothies", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
//Should I be even doing this fetch GET request on smoothie.ejs?
})
Smoothies is the protected route. When I try to use Postman and send a GET request to (/smoothies) using authorization : bearer token, it works and I am able to access /smoothies. However, if I try on the real application, I was denied access even with access token in my sessionStorage. When I console.log req.headers['authorization'], it was undefined so I am guessing my GET request from smoothie.ejs does not work. Does anyone know what is the solution?
After adding authentication to our backend Graphql server the "Schema" and "Docs" are no longer visible in the Graphql Playground. Executing queries when adding a token to the "HTTP HEADERS" in the Playground does work correctly when authenticated and not when a user isn't authenticated, so that's ok.
We disabled the built-in Playground from Apollo-server and used the middleware graphql-playground-middleware-express to be able to use a different URL and bypass authentication. We can now browse to the Playground and use it but we can't read the "Schema" or "Docs" there.
Trying to enable introspection didn't fix this. Would it be better to call passport.authenticate() in the Context of apollo-server? There's also a tool called passport-graphql but it works with local strategy and might not solve the problem. I've also tried setting the token in the header before calling the Playground route, but that didn't work.
We're a bit lost at this. Thank you for any insights you could give us.
The relevant code:
// index/ts
import passport from 'passport'
import expressPlayground from 'graphql-playground-middleware-express'
const app = express()
app.use(cors({ origin: true }))
app.get('/playground', expressPlayground({ endpoint: '/graphql' }))
app.use(passport.initialize())
passport.use(bearerStrategy)
app.use(
passport.authenticate('oauth-bearer', { session: false }),
(req, _res, next) => { next() }
)
;(async () => {
await createConnections()
const server = await new ApolloServer({
schema: await getSchema(),
context: ({ req }) => ({ getUser: () => req.user, }),
introspection: false,
playground: false,
})
server.applyMiddleware({ app, cors: false })
app.listen({ port: ENVIRONMENT.port }, () => { console.log(`Server ready`) })
})()
// passport.ts
import { IBearerStrategyOptionWithRequest, BearerStrategy, ITokenPayload } from passport-azure-ad'
import { Account } from '#it-portal/entity/Account'
export const bearerStrategy = new BearerStrategy( config,
async (token: ITokenPayload, done: CallableFunction) => {
try {
if (!token.oid) throw 'token oid missing'
const knownAccount = await Account.findOne({ accountIdentifier: token.oid })
if (knownAccount) return done(null, knownAccount, token)
const account = new Account()
account.accountIdentifier = token.oid
account.name = token.name
account.userName = (token as any).preferred_username
const newAccount = await account.save()
return done(null, newAccount, token)
} catch (error) {
console.error(`Failed adding the user to the request object: ${error}`)
}
}
)
I figured it out thanks to this SO answer. The key was not to use passport as middleware on Express but rather use it in the Graphql Context.
In the example code below you can see the Promise getUser, which does the passport authentication, being used in the Context of ApolloServer. This way the Playground can still be reached and the "Schema" end "Docs" are still accessible when run in dev mode.
This is also the preferred way according to the Apollo docs section "Putting user info on the context".
// apollo.ts
passport.use(bearerStrategy)
const getUser = (req: Express.Request, res: Express.Response) =>
new Promise((resolve, reject) => {
passport.authenticate('oauth-bearer', { session: false }, (err, user) => {
if (err) reject(err)
resolve(user)
})(req, res)
})
const playgroundEnabled = ENVIRONMENT.mode !== 'production'
export const getApolloServer = async () => {
return new ApolloServer({
schema,
context: async ({ req, res }) => {
const user = await getUser(req, res)
if (!user) throw new AuthenticationError('No user logged in')
console.log('User found', user)
return { user }
},
introspection: playgroundEnabled,
playground: playgroundEnabled,
})
}
The best thing is that you only need two functions for this to work: passport.use(BearerStrategy) and passport.authenticate(). This is because sessions are not used so we don't need to add it as Express middleware.
// index/ts
const app = express()
app.use(cors({ origin: true }))
;(async () => {
await createConnections()
const server = await getApolloServer()
server.applyMiddleware({ app, cors: false })
app.listen({ port: ENVIRONMENT.port }, () => { console.log(`Server ready`) })
})()
I hope this helps others with the same issues.
I have an express route in which I send a header from the front end, in this route I'm making a GET request using axios. I created an interceptor with axios, but I would like to be able to read the req object from the activated route in order to add the header to the axios GET call.
// Example Interceptor
axios.interceptors.request.use(
config => {
// How to get req.headers from the route here?
return config;
},
error => {
return Promise.reject(error);
}
);
// Exemple GET route
router.get('/get', async (req, res, next) => {
try {
const { data } = await axios.get('https://kjhf.fsadjhfewq.....');
} catch (error) {
console.log(error)
}
res.status(200).json({});
});
Is it possible to do this?
So I think the way to do this is to use a middleware to set the headers, and pass on the axios instance
// apiSetHeader.js middleware
exports.default = (req, res, next) => {
req.CustomAxios = axios.create({
headers: { 'HeaderForTheApi': req.headers.apiHeader'}
})
next()
}
And then use that in your route
// Exemple GET route
router.get('/get', apiSetHeaderMiddleware, async (req, res, next) => {
try {
const { data } = await req.CustomAxios.get('https://kjhf.fsadjhfewq.....');
} catch (error) {
console.log(error)
}
res.status(200).json({});
});
Hope this helps!
I'm making a webapp that uses Socket.io to pass information between the server and the client, one example being login information. The documentation for passport.authenticate says to use it like so:
app.post('/login', passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login' }));
However, my webapp is using Polymer client-side routing, so the only route my index.js has is this:
app.get('*', function (req, res) {
res.sendFile('./public/index.html', {root: '.'});
});
Instead, I'd like to do something like this:
io.on('connection', function(socket){
socket.on('login', function(data){
passport.authenticate('local', data);
});
});
However, this doesn't work as the authenticate function doesn't even get called right now. Is there a way to make passport work in such a scenario?
You can try something like below .
In your routes define and require the socket module, so you have access to use it in routes.
var express = require('express');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
var router = express.Router();
var passport = require('passport');
io.on('connection', function(socket){
socket.on('login', function(data){
// call the routes
router.post('/login', function(request, response, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
// return next(err);
socket.emit('loginResult', { success: false });
}
if (!user) {
var message = "Invalid credentials";
socket.emit('loginResult', { success: false , message: message});
}
request.logIn(user, function (err) {
if (err) {
socket.emit('loginResult', { success: false });
}
// if want to save user in session
request.session.user = user;
// after success code
socket.emit('loginResult', { success: true , user : user});
});
})(request, response, next);
});
});
});
Hope this helps.
You can define your custom callback with passport.authenticate(). I have given a example below, you might wanna try that. Go here for more info.
io.on('connection', function(socket){
socket.on('login', function(data){
var req = {}
req.body = data
passport.authenticate('local', function(err, user, info) {
if (err) {
socket.emit('login', { success: false });
}
if (!user) {
socket.emit('login', { success: false });
}
// Set session
req.logIn(user, function(err) {
if (err) {
socket.emit('login', { success: false });
}
socket.emit('login', { success: true });
});
});
});
Update: Problem with previous code was, when using custom callbacks in passport authenticate it uses req object from the closure, which in this case was undefined as it was not in the router. I think, now that you can provide enough authentication data through req.body it should work.