Can't access token saved to cookies using passport, express and jsonwebtokens.
I'm using passport for authorization and passport-jwt to authentication of web-tokens. I have verified that my server is issuing web-tokens and setting cookies on the browser, but when I attempt to use secure routes it gives me an unauthorized message.
...
// fetching from server
const response = fetch("http://localhost:5000/user/profile");
...
...
app.use(cors({ credentials: true, origin: "http://localhost:3000" }));
app.use(cookieParser("password"));
app.use("/",require("./routes/routes"));
app.use("/user",passport.authenticate("jwt",
{session:false},require("./routes/secure-routes"));
...
...
router.post("/login",async(req,res)=>{
passport.authenticate("login",{session:false},async (err,user)=>{
...
req.login(payload,{session:false},async error=>{
...
const token = jwt.sign(JSON.stringify(payload),"password");
res.cookie("jwt",token,{httpOnly:true});
res.status(200).send({msg:"cookie set!});
}}
})
...
...
const JWTstrategy = require("passport-jwt").Strategy;
passport.use(
new JWTstrategy(
{
jwtFromeRequest: req=>req.cookies.jwt,
secretOrKey: "password"
},
(jwtPayload, done) => {
return done(null,jwtPayload);
}
)
...
The server is definitely setting the cookies on the browser to the webtoken, but for some reason I can't retrieve the token from the GET route. Any help would be greatly appreciated.
You need to include the cookies.
const response = fetch("http://localhost:5000/user/profile", {
credentials: "include"
});
Related
Here is the situation:
I have a database which contains a user and password registered.
My assignment, for now, is to create a login form, and login with a registered uname and pw.
Uname and pw are registered in the server/database already.
ps: I did not create the server nor database.
Node server code
import express from 'express';
import cors from 'cors';
import http from 'http';
import { Sequelize } from 'sequelize';
import { Data } from './database';
import { router } from './routes/Router';
import { initialData } from './database/someData';
const closeServer = async (
server: http.Server,
sequelize: Sequelize,
signal?: string
) => {
server.close();
await sequelize.close();
process.exit();
};
const runServer = async (): Promise<void> => {
const PORT = process.env.PORT || 8082;
const app = express();
const sequelize = Data.init();
app.use(
cors({
credentials: true,
origin: 'http://localhost:3000',
})
);
app.use('/api', router);
const server = app.listen(PORT, () => {
console.log(`Starting server at ${PORT}`);
});
try {
await sequelize.authenticate();
await sequelize.sync({
force: process.env.SERVER === 'reset',
});
if (process.env.SERVER === 'reset') await initialData();
} catch (e) {
closeServer(server, sequelize);
throw e;
}
};
runServer()
.then(() => {
console.log('Run successfully');
})
.catch((ex: Error) => {
console.log('Unable to run:', ex);
});
I need help on what is that I have to do.
When I input username and pw, on the form, what are the methods to use for sending the info?
And then, when the info reaches the server, i think the username and pw need to be validated with jwt, and then check if the user and pw exists. how do i do that?
What i have understood so far is that i gotta use axios to send info to server, but thats it.
Do i need to use jwt for the login?
What is the normal flow for this kind of mechanism?
I am using react as a framework.
So there are quite few steps here.
First you have to create endpoint on your backend server for issuing jwt tokens. Jwt tokens can be used as a pass for user to login. So in your router you would add something like this:
router.post('/login', (req, res)=> {
const username = req.body.username
const password = req.body.password
// Then you make db call to verify username and password are correct.
if Credentials are valid, you would issue jwt token
jwt.sign({
// here you can save extra information of user. Also remember this information must be public since anyone can see it. Do not put user password here
email: 'email',
userId: 'id',
}, "secret")
})
After this, you need some kind of middleware on backend, so that on each user request, you check and verify this jwt token which is sent from react application. For example you could write isAuth middleware:
const jwt =require("jsonwebtoken");
export const isAuth= (req, res, next) => {
try {
// here we attach request in auth header, with Bearer "jwt token" format. So we extract jwt token and verify it
const authHeader = req.get("Authorization");
if (!authHeader) {
return res.status(401).json({ message: "no token" });
}
const token = authHeader.split(" ")[1];
let decodedToken;
decodedToken = jwt.verify(token, "secret");
if (!decodedToken) {
return res.status(401).json({ message: "Wrong token" });
}
req.userId = decodedToken.userId;
next();
} catch (err) {
console.error(err);
return res.status(401).json({ message: err });
}
};
Now you would be able to have backend endpoints like this:
// This is how you would require login on some routes
router.post("/getMyPrivateInfo", isAuth, QueryPrivatInfo)
Now on React side, you would make request for login like this:
axios.post("/login", {
username: '1',
password: "2"
})
This would return jwt token, now you would save this token in local storage.
After its saved in local storage and you make request with axios for private info you would do following
axios.post("/getMyPrivateInfo", {any request body info neeeded}, {
headers: {
Authorization: "Bearer jwtTokenFromLocalStorage"
}
})
This is how whole flow will work, hope it makes sense
I have my back-end Express.js server that has sign in function. After user sign in, he gets 2 tokens - access token and refresh token. What I want to do, is to make return from server refresh token as httpOnly cookie.
Here is a peace of code of this function:
const { refreshToken, accessToken } = await jwtService.updateTokens({
userId: client.id, username: client.username
}, { transaction })
logger.info(`Client ${client.email} has been successfully signed in!`)
await transaction.commit()
return res
.status(200)
.cookie("refreshToken", JSON.stringify(refreshToken), { httpOnly: true, secure: false })
.json({ accessToken, reopening: reopening ? client.username : null })
Basically, browser just doesn't set this cookie as httpOnly and doesn't set it at all, actually. So, I was trying to ping this endpoint with postman, and it works:
In reponse body I have access token and in httpOnly cookie I have this refresh token.
So, the problem is, why browser doesn't save it? I have found a couple of solutions and all of them were about cors and axios credentials. I use 2 express servers - 1 is for normal back-end and 1 is for front-end.
Here is how "path" from front-end to back-end looks like:
Sign in function that send request to front-end express server:
const api = axios.create({
baseURL: apiUrl,
headers: {
'Content-Type': 'application/json'
}
})
export const signIn = async payload => {
try {
const { data } = await api.post('s-i', payload)
return data
} catch (e) {
return e.response.data
}
}
Front-end express server sends request to actual back-end:
const api = axios.create({
baseURL: process.env.NODE_ENV === "development" ? process.env.PNB_API_DEV : process.env.PNB_API_PROD,
})
const router = Router()
router.post('/s-i', async (req, res) => {
try {
const { data } = await api.post('/sign-in', req.body)
res.json(data)
} catch (e) {
return res.status(e.response.status).json(e.response.data)
}
});
And then that function that was at the very begging.
So - the question is - how to make browser save those httpOnly cookies? If it's really about credentials or cors where should I put those settings?
PS
Back-end port - 3001 and front-end port - 8010.
I have a Vue.js SPA and a Node.js API built with Express.js. I'm using express-session (^1.11.3) to manage sessions and express-sequelize-session (0.4.0) to persist the session on a Postgres DB through Sequelize because I need a session to be able to use passport-azure-ad with oidc strategy.
I was having some issues logging in with Microsoft accounts after some time and came to the conclusion that it is because the session cookie (connect.sid) is never cleared from the browser.
I had some things misconfigured and made some changes but even with all the changes it is still not working.
Session on my Express app is configured in the following way:
import session from 'express-session';
import expressSequelizeSession from 'express-sequelize-session';
const Store = expressSequelizeSession(session.Store);
app.use(session({
cookie: {
path: '/',
httpOnly: true,
secure: env !== 'development', // On environments that have SSL enable this should be set to true.
maxAge: null,
sameSite: false, // Needs to be false otherwise Microsoft auth doesn't work.
},
secret: config.secrets.session,
saveUninitialized: false,
resave: false,
unset: 'destroy',
store: new Store(sqldb.sequelize),
}));
On the FE I'm using Vue.js with Axios and setting withCredentials to true so that the cookie is passed on the HTTP request.
// Base configuration.
import Axios from 'axios';
Axios.defaults.baseURL = config.apiURL;
Axios.defaults.headers.common.Accept = 'application/json';
Vue.$http = Axios;
// When making request.
Vue.$http[action](url, payload, { withCredentials: true }).then(() => // Handle request);
You can see from the image that the cookie is being sent on the logout request.
When logging out I'm hitting this endpoint and destroying the session as is explained on the documentation.
router.post('/logout', (req, res) => {
try {
req.session.destroy(() => {
return responses.responseWithResultAsync(res); // Helper method that logs and returns status code 200.
});
return responses.handleErrorAsync(res); // Helper method that logs and returns status code 500.
} catch (error) {
return responses.handleErrorAsync(res, error); // Helper method that logs and returns status code 500.
}
});
The interesting thing is that the session on the DB is removed so I know that the cookie is being sent properly on the request with the right session ID but it is not removing it on the browser for some reason. After logging out I still have this:
Does anyone know what I'm doing wrong? I find it odd that the session is being removed on the DB successfully but not on the request.
As #RolandStarke mentioned the express-session library doesn't have the built in functionality to remove the cookie from the browser, so I just did it manually in the following way:
router.post('/logout', (req, res) => {
try {
if (req.session && req.session.cookie) {
res.cookie('connect.sid', null, {
expires: new Date('Thu, 01 Jan 1970 00:00:00 UTC'),
httpOnly: true,
});
req.session.destroy((error) => {
if (error) {
return responses.handleErrorAsync(res, error);
}
return responses.responseWithResultAsync(res);
});
}
return responses.responseWithResultAsync(res);
} catch (error) {
return responses.handleErrorAsync(res, error);
}
});
I am building a simple app with React as frontend and Node/Express/MongoDB as backend. I am authenticating user using Passport. Local authentication is working, as well as Google authentication.
I just seem to not able to load the google login page through the app. I am getting CORS error. I have shared the error below.
On React Login page:
const onClick = async () => {
await Axios.get('/auth/google');
};
Proxy Middleware:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(createProxyMiddleware('/auth', { target: 'http://localhost:4000' }));
};
Node Server.js:
app.use('/auth', require('./routes/auth'));
routes/auth file:
const cors = require('cors');
var corsOptions = {
origin: 'http://localhost:3000',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
};
router.get(
'/google',
cors(corsOptions),
passport.authenticate('google', {
scope: ['profile', 'email'],
}),
);
router.get('/google/redirect',cors(corsOptions), passport.authenticate('google'), (req, res) => {
res.send(req.user);
});
passportconfig.js:
passport.use(
new GoogleStrategy(
{
clientID: ClientID,
clientSecret: ClientSecret,
callbackURL: '/auth/google/redirect',
proxy: true,
},
(accessToken, refreshToken, profile, done) => {
// passport callback function
//check if user already exists in our db with the given profile ID
User.findOne({ googleId: profile.id }).then((currentUser) => {
if (currentUser) {
//if we already have a record with the given profile ID
done(null, currentUser);
} else {
//if not, create a new user
new User({
googleId: profile.id,
})
.save()
.then((newUser) => {
done(null, newUser);
});
}
});
},
),
);
Error:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fgoogle%2Fredirect&scope=profile%20email&client_id=<clientID>.apps.googleusercontent.com' (redirected from 'http://localhost:3000/auth/google') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If I click on the above XMLHttpRequest link, I am able to authenticate and an account is created on my DB with googleID.
I have tried different options suggested throughout internet, but none of them is working for me. I am not sure what is going wrong here.
According to the documentation, try removing the corsOptions entirely and just use the cors() function in your express middle-ware before any router is declared. Like so:
app.use(cors());
Let me know if this works.
// 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.
UPDATE
I think it's worth mentioning I am running Angular CLI which runs on port 4200 and my server is running on port 8080. Could this be a problem? It's the only thing I can think of at the moment
When I make a call to my route '/auth/login' I set a loggedIn property on the session object. To check a user is authenticated, a request is made to '/auth/checktoken'. In here, I check for the presence of the loggedIn property on the req.session object. When I do these requests within Postman everything works perfectly fine, but when using the browser my session.loggedIn property is undefined. I will paste the relevant code below. Thanks in advance for any help
Server Side
router.get('/checktoken', (req, res) => {
if(!req.session.loggedIn) {
return res.status(401).send({
userTitle: 'Not authorised',
userMessage: 'You are not authorised to view this'
})
}
return res.status(200).send()
})
Client Side
#Injectable()
export class CheckAuthenticationService implements CanActivate {
constructor(
private router: Router,
private http: HttpClient) { }
canActivate() {
this.http.get('http://localhost:8080/auth/checktoken', { responseType: 'text' })
.toPromise()
.then(() => {
this.router.navigate(['admin']);
})
.catch( () => {
this.router.navigate(['login']);
});
return true;
}
}
Snippet of login code that sets the loggedIn property
if (user) {
user.comparePassword(password, (err, isMatch) => {
if (isMatch && isMatch) {
req.session.loggedIn = user;
res.status(200).send()
} else {
res.status(404).send({
userTitle: 'Wrong password',
userMessage: 'Please make sure your password is correct'
});
}
});
}
Session Store setup
app.use(session({
name: 'jack-thomson',
secret: SECRET_KEY,
saveUninitialized: false,
resave: true,
store: new MongoStore({
mongooseConnection: mongoose.connection
})
}))
This all works in Postman but when hitting these endpoints on the client, .loggedIn is undefined, always
I had the same problem before. I think it's about cors credential. I use Axios on React to POST data login to my Express backend application. I need to add these lines:
import axios from 'axios';
axios.defaults.withCredentials = true;
Then on my Express project, I add cors:
var cors = require('cors');
app.use(cors({
credentials: true,
origin: 'http://localhost:3000' // it's my React host
})
);
Finally I can call my login function as usual, for instance:
signup(){
var url = 'http://localhost:3210/'
axios.post(url, {
email: this.refs.email.value,
username: this.refs.username.value,
password: this.refs.password.value,
passwordConf: this.refs.passwordConf.value
})
.then((x)=>{
console.log(x);
if(x.data.username){
this.setState({statusSignup: `Welcome ${x.data.username}`});
} else {
this.setState({statusSignup: x.data});
}
})
.catch((error)=>{console.log(error)})
}
login(){
var url = 'http://localhost:3210/';
var data = {
logemail: this.refs.logemail.value,
logpassword: this.refs.logpassword.value,
};
axios.post(url, data)
.then((x)=>{
console.log(x);
if(x.data.username){
this.setState({statusLogin: `Welcome ${x.data.username}`});
} else {
this.setState({statusLogin: x.data});
}
})
.catch((error)=>{console.log(error)})
}
And it works! Hope this solve your problem.
Are you using CORS?
I had the same problem, and i solved it by putting { withCredentials: true } as optional arguments in every request.
I mean whenever you send a http/https request in your service, put this as last argument, and you are good to go.
You can read this and this Stackoverflow question for more information on the topic.
I have finally figured out what is going on. My Angular CLI was running on 4200 and my server was running on a separate port. I have gotten over the issue with serving my application with express so it is all one one route. This has solved the issue for me. If anyone comes by this I hope this information comes in handy to you!