res.clearCookie - Delete a JWT token - node.js

I'm trying to set-up a route that delete cookie containing a JWT on the client's browser.
To perform that, I'm using the res.ClearCookie function
public async logOut (req: Request, res: Response) {
res.clearCookie('auth-token', {httpOnly: true, path:'/', domain: 'localhost'});
console.log('cookie deleted')
}
I have seen that clearCookie function has to contain the same object that I passed during it creation, so here is the way I created it
const accessToken: string = jwt.sign({id: existingUser.id}, process.env.ACCESS_TOKEN_SECRET || 'tokensecret' )
return res.cookie('auth-token', accessToken, {httpOnly: true, path:'/', domain: 'localhost'}).json(mainWallet[0].id)
This way, the cookie isn't deleted when I try to logout.
Do you have an idea to fix that ?
Thanks,
Paul

I have followed a new tutorial that say to setup the log-out route this way. this time it's works
res.status(202).clearCookie('auth-token').send('cookie cleared')

use these set of codes to construct your Api. this is the cleanest and shortest i have found for logout
export const logout = async (req, res, next) => {
res.clearCookie("jwt");
res.redirect("/");
};
This only clear the tokens stored in the cookie

Related

How to obtain the token from the current user with jwt/express/node

I have a controller that receives an user that is trying to login via form. When all validations are checked, the user will be logged in and a token will be created in the following way:
const token = jwt.sign({userId: user._id}, config.secret ,{expiresIn: '24h'})
res.json({success: true, message: 'SesiĆ³n iniciada', token: token, user: {email: user.email}})
However, how do I access this token from another controller? I've seen that a good approach would be to create a middleware that intercepts such token, but I don't really know how to accomplish this.
I'd be happy only knowing how to get the token tho. I'm kinda new and I'm taking very small steps.
You should setup your client requests to send such token as #Vahid said.
Here's an example with axios
const instance = axios.create({
baseURL: 'https://some-domain.com/api',
// From the docs:
// `transformRequest` allows changes to the request data before it is sent to the server
// This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'
// The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
// FormData or Stream
// You may modify the headers object.
transformRequest: [function (data, headers) {
headers['Authorization'] = localStorage.getItem('jwt')
return data;
}],
})
export default instance
In case you also need GET request you can add:
export setAuthToken = (token) => {
instance.defaults.headers.common['Authorization'] = token;
}
Although you'll need to call it every time your JWT is renewed.
After that, you could catch it using the Middlewares to decode the token from the headers
app.use((req, res, next) => {
const authToken = req.headers['Authorization']
if(authToken) {
try {
const decoded = jwt.verify(authToken, config.secret)
req.user = decoded.userId
// Hopefully
// req.user = getUserById(decoded.userId)
next()
} catch(e) {
// Handle Errors or renewals
req.user = null
// You could either next() to continue or use 'res' to respond something
}
} else {
// Throw 403 if should be authorized
res.sendStatus(403)
}
})
This way you should be able to access req.user on any route defined after your middleware.
Eg:
app.post('/me', (req, res) => {
res.send(req.user)
})
Note that this is just one example of a global middleware. In other cases, you should be able to create custom middlewares based on which routes you want to protect or with which amount of permissions.

ExpressJS/Passport-SAML Single Log Out re-logs in directly

Currently I am working on a passport-saml implementation in our NodeJS application.
The reason to do so is to give our customers the possibility to connect to their AD FS systems and take advantage of SingleSignOn(SSO).
As we also want to give logout functionality I was working on that logic. However, I can't seem to get this simple piece of functionality working. I have already googled a lot, tried a lot of variations and configurations but unfortunately, it does not work.
I would like to give our customers the possibility to SingleLogOut (SLO) that is both SP and IdP driven. This was my starting point. During the course of debugging and development I already took a step back and tried to kill the local session but even that is not possible it seems.
This is the relevant code from the routes I configured for SAML:
const isAuthenticated = (req, res, next) => {
if (req.isAuthenticated()) {
// User logged in, pass on to next middleware
console.info('User authenticated');
return next();
}
// User not logged in, redirect to login page
console.info('User not authenticated');
return res.redirect('/login');
};
// GET-routes
app.get('/',
isAuthenticated,
(req, res) => {
res.send('Authenticated');
});
app.get('/login',
passport.authenticate('saml', {
successRedirect: '/',
failureRedirect: '/login/fail',
}));
app.get('/logout',
(req, res) => {
passport._strategy('saml').logout(req, (err, url) => {
return res.redirect(url);
});
});
// POST-routes
app.post('/adfs/callback',
(req, res, next) => {
passport.authenticate('saml', (err, user) => {
// If error occurred redirect to failure URL
if (err) return res.redirect('/login/fail');
// If no user could be found, redirect to failure URL
if (!user) return res.redirect('/login/fail');
// User found, handle registration of user on request
req.logIn(user, (loginErr) => {
if (loginErr) return res.status(400).send(err);
// Request session set, put in store
store.set(req.sessionID, req.session, (storeErr) => {
if (storeErr) return res.status(400).send(storeErr);
return res.redirect('/');
});
});
})(req, res, next);
});
app.post('/logout/callback', (req, res) => {
// Destroy session and cookie
store.destroy(req.sessionID, async (err) => {
req.logout();
return res.redirect('/');
});
});
As can be seen I took control of the session store handling (setting and destroying sessions, but if this is unwise please advise).
The session store implemented is the MemoryStore (https://www.npmjs.com/package/memorystore).
What happens is that, when a user is logged in everything works fine.
Then a request is sent to route /logout, and some stuff happend and I can see the session changing, the session ID gets changed as well as relevant parameters for passport-saml (nameID, sessionIndex) and the user is then rerouted to '/'.
However then the user is seen as not authenticated and rerouted to '/login'. One would argue that it stops here, as the credentials have to be re-entered.
This is not the case, as the user is directly logged in again, without re-entering credentials and I do not know how to prevent this.
I do hope anybody knows what's going on :)
If there is need for additional information I would like to hear gladly.
So after much research and investigation I did found the solution for this problem.
The trick was in the definition of the passport-saml package, in particular the authncontext parameter.
So previously I had the SamlStrategy options defined as:
{
// URL that should be configured inside the AD FS as return URL for authentication requests
callbackUrl: `<URL>`,
// URL on which the AD FS should be reached
entryPoint: <URL>,
// Identifier for the CIR-COO application in the AD FS
issuer: <identifier>,
identifierFormat: null,
// CIR-COO private certificate
privateCert: <private_cert_path>,
// Identity Provider's public key
cert: <cert_path>,
authnContext: ["urn:federation:authentication:windows"],
// AD FS signature hash algorithm with which the response is encrypted
signatureAlgorithm: <algorithm>,
// Single Log Out URL AD FS
logoutUrl: <URL>,
// Single Log Out callback URL
logoutCallbackUrl: `<URL>`,
}
But after much research I realised this authentication:windows option was the culprit, so I changed it to:
{
// URL that should be configured inside the AD FS as return URL for authentication requests
callbackUrl: `<URL>`,
// URL on which the AD FS should be reached
entryPoint: <URL>,
// Identifier for the CIR-COO application in the AD FS
issuer: <identifier>,
identifierFormat: null,
// CIR-COO private certificate
privateCert: <private_cert_path>,
// Identity Provider's public key
cert: <cert_path>,
authnContext: ["urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
"urn:federation:authentication:windows"],
// AD FS signature hash algorithm with which the response is encrypted
signatureAlgorithm: <algorithm>,
// Single Log Out URL AD FS
logoutUrl: <URL>,
// Single Log Out callback URL
logoutCallbackUrl: `<URL>`,
},
Which basically means it won't retrieve the Windows credentials of the user that is logged onto the system by default, thus redirecting to the login screen of the ADFS server.
Using authnContext: ["urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"] in the session setup will show the username and password page again after logging out.
Logout achieved https://adfs-url/adfs/ls/?wa=wsignout1.0.
A new session-id is created after signing in again.

PassportJS openid-client How to use without a session, save token/user in normal cookie

We are currently using the https://github.com/panva/node-openid-client strategy for passportJS, along with cookie-session.
However we would like to try and move away from sessions and just store either a simple cookie with a token, or attach the token to a header on each request which we then introspect to see whether the token is valid.
I can't figure it out, maybe it's not possible, I just simply don't know where or how I can retrieve the token from the openid-client library, and when and how I should save it in a cookie. Maybe it is only built to use a session.
currently we have:
passport.use(
`oidc.${site}`,
new Strategy(
{
client,
params: getParamsForSite(site),
passReqToCallback,
usePKCE,
},
(tokenset, done) => {
const user = {
token: tokenset,
name: tokenset.claims.sub,
};
return done(null, user);
}
)
);
for the login
app.get(['/login', '/login/:site'], (req, res, next) => {
if (req.params.site) {
passport.authenticate(`oidc.${req.params.site}`)(req, res, next);
} else {
res.end(loginFrontend.success());
}
});
and for the callback
app.get('/auth_callback', (req, res, next) => {
passport.authenticate(`oidc.${req.query.state}`, {
callback: true,
successReturnToOrRedirect: process.env.BASE_URI,
})(req, res, next);
});
We would like to continue using this library as the authentication service we call has a discovery endpoint etc. and wouldn't want to implement all of the features ourselves. If I set session to false, how do I retrieve the token and where for this strategy, can someone help me?

Facebook-passport with JWT

I've been using Passport on my server for user authentication.
When a user is signing in locally (using a username and password), the server sends them a JWT which is stored in localstorage, and is sent back to server for every api call that requires user authentication.
Now I want to support Facebook and Google login as well. Since I began with Passport I thought it would be best to continue with Passport strategies, using passport-facebook and passport-google-oauth.
I'll refer to Facebook, but both strategies behave the same. They both require redirection to a server route ('/auth/facebook' and '/auth/facebook/callback' for that matter).
The process is successful to the point of saving users including their facebook\google ids and tokens on the DB.
When the user is created on the server, a JWT is created (without any reliance on the token received from facebook\google).
... // Passport facebook startegy
var newUser = new User();
newUser.facebook = {};
newUser.facebook.id = profile.id;
newUser.facebook.token = token; // token received from facebook
newUser.facebook.name = profile.displayName;
newUser.save(function(err) {
if (err)
throw err;
// if successful, return the new user
newUser.jwtoken = newUser.generateJwt(); // JWT CREATION!
return done(null, newUser);
});
The problem is that after its creation, I don't find a proper way to send the JWT to the client, since I should also redirect to my app.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
successRedirect : '/',
failureRedirect : '/'
}), (req, res) => {
var token = req.user.jwtoken;
res.json({token: token});
});
The code above redirects me to my app main page, but I don't get the token.
If I remove the successRedirect, I do get the token, but I'm not redirected to my app.
Any solution for that? Is my approach wrong? Any suggestions will do.
The best solution I found for that problem would be to redirect to the expected page with a cookie which holds the JWT.
Using res.json would only send a json response and would not redirect. That's why the other suggested answer here would not solve the problem I encountered.
So my solution would be:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
successRedirect : '/',
failureRedirect : '/'
}), (req, res) => {
var token = req.user.jwtoken;
res.cookie('auth', token); // Choose whatever name you'd like for that cookie,
res.redirect('http://localhost:3000'); // OR whatever page you want to redirect to with that cookie
});
After redirection, you can read the cookie safely and use that JWT as expected. (you can actually read the cookie on every page load, to check if a user is logged in)
As I mentioned before, it is possible to redirect with the JWT as a query param, but it's very unsafe.
Using a cookie is safer, and there are still security solutions you can use to make it even safer, unlike a query param which is plainly unsecure.
Adding to Bar's answer.
I prepared a landing component to extract the cookie, save it to local storage, delete the cookie, then redirect to an authorized page.
class SocialAuthRedirect extends Component {
componentWillMount() {
this.props.dispatch(
fbAuthUser(getCookie("auth"), () => {
document.cookie =
"auth=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
this.props.history.push("/profile");
})
);
}
render() {
return <div />;
}
}
A proper solution would be to implement the redirection on the client side.
Simply use:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
failureRedirect: '/login'
}), (req, res) => {
res.json({
token: req.user.jwtoken
})
}
)
If you're client side receives the token, then redirect from there to home page, and in the case the login wasn't successful, it would be redirected by the server directly.
Or you can go for the full client side management as I would:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false
}), (req, res) => {
if (req.user.jwtoken) {
res.json({
success: true,
token: req.user.jwtoken
})
} else {
res.json({
success: false
})
}
}
)
If success === true, store JWT in LocalStorage, else redirect to login page.

Passport-jwt authenticate not working well with node-jwt-simple

I'm using passport-jwt to authenticate some routes and I'm creating my jwts with node-jwt-simple/jwt-simple but facing some difficulties cause it looks like my passport-jwt authenticate middleware is not being called at all.
Here is my
passport-jwt-strategy
const jwtOpts = {
jwtFromRequest: ExtractJwt.fromHeader('Authorization'),
secretOrKey: secret,
};
passport.use(new jwtStrategy(jwtOpts, (payload, done) => {
console.log('payload ', payload.sub);
User.findById(payload.sub, (err, user) => {
if(err) { return done(err); }
if(!user) { console.log('didnt find!'); return done(null, false); }
done(null, user);
});
}));
which i'm then integrating it over here.
routes file
router.get('/success',
passport.authenticate('jwt', {session: false}),
async (ctx, next) => ctx.body = await "success!");
Here is also the way I make my jwt.
function tokenForUser(user) {
const timeStamp = new Date().getTime;
return jwt.encode({sub: user._id, iat: timeStamp}, secret);
}
//- Later in signup process
userToSave.save(async(err, user) => {
if(err) { return next(err); }
const token = await tokenForUser(user);
next(token);
});
//- If this helps, here is how my secret file looks like.
const secret = "JKAha23ja1ddHdjjf31";
export default secret;
Problem comes, when I hit that route i only get Unauthorized and in the console nothing gets logged out not even the 'payload' key I specified first.
I should also say that I have the token at ctx.request.get('Authorization') (Koa based) i think it's something like req.header('Authorization') with express in all routes.
Also The exact express based problem can be found on the github issues of node-jwt-simple here incase there is any problem with my code samples.
Thank you.
After I wrapped my head right i knew that this has been my horrible understanding of how the whole authentification process works.
When I decoded the token from ctx.get('Authorization') I got a different _id than the one stored in the db Because I had hardcoded Authorization header in postman and thought "If I ctx.set('Authorization', token); It will replace the one I hardcoded on postman".
Less did I think that this jwt will be included in a header of requests when I make http calls on front end.
I naively thought jwts are passed directly from the server to the browser (Something like how render works) and Not from the server to an ajax process which later embeds it in request made which is the correct way.
The whole code is awesome, except now I have to just pass the token ctx.body = token; after I created it when I signed up.
Thank You.

Resources