Simple JWT authenticated mern app (here) in place using redux and create-react-app. I want to add passport-steam as an authentication method, however I'm not sure how to handle adding a JWT to the passport-steam strategy. Below is what I have:
// Steam
router.get('/steam', passport.authenticate('steam', { session: false }));
router.get(
'/steam/return',
passport.authenticate('steam', { session: false }),
(req, res) => {
const user = req.user
jwt.sign(
{ id: user.id },
'JWT_secret',
{ expiresIn: '2h' },
(err, token) => {
if (err) throw err;
res.json({
user: user,
token,
});
}
);
res.redirect('/')
}
)
Using the default steam strategy It works and user data is added to the DB, but there's no token in local storage. I'm not sure how to do it with steam as I'm not dispatching an action/not sure If I can. Do I need to authenticate via steam, grab and save the data to the database and then dispatch another action to retrieve it and add the JWT, or is there a better method?
Related
I have a backend API for handling users with JWT and passsport, using Node.js and Express. The frontend is on Vue and everything works alright, except when a new User signs in, the backend authenticator seems to get a jwt_payload that has an id (which is supposed to be the user's id) that doesn't correspond to anybody. Upon refreshing the page, suddenly the jwt_payload has the correct Id and can find a user. The following is the code for the authenticator and JWT strategy:
JWTStrategy:
module.exports = passport => {
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
User.findById(jwt_payload.id)
.then(user => {
if(user) return done(null, user);
return done(null,false);
})
.catch(err =>{
return done(err, false, {message:"Hubo un error en el servidor"});
});
})
);
};
Authenticator:
module.exports = (req,res,next) => {
passport.authenticate("jwt", function(err,user,info){
if(err) return next(err);
if(!user) return res.status(401).json({message:"Acceso denegado - No token"});
req.user = user;
next();
})(req,res,next);
};
I followed a guide on how to set up jwt authentication and authorization, users can sign in an register as normal. If it is useful, the Vue frontend checks for a jwt token in localStorage as a second barrier for authorization to the home page, upon openning the home page (which has data that depends on the current user), I get that 401 from the authenticator. I am using vue router to push the home page when the user logs in (logging in saves the jwt token to the localStorage).
I have an iOS app and nodeJS backend. Currently I have implemented passport-facebook strategy. From the app I get the facebook token, and I send it to backend where I authorise the user.
// config
var FacebookTokenStrategy = require('passport-facebook-token');
const passport = require('passport')
const { facebook_client_id, facebook_client_secret } = require('../config')
passport.use(new FacebookTokenStrategy({
clientID: facebook_client_id,
clientSecret: facebook_client_secret,
}, function (accessToken, refreshToken, profile, done) {
done(null, profile)
}
));
And the middleware
const passport = require('passport')
require('../config/passport-facebook')
require('../config/passport-apple')
require('../config/passport')
const { INVALID_TOKEN, UNAUTHORIZED } = require('../config/constants')
module.exports = (req, res, next) => {
passport.authenticate(['apple','facebook-token', 'jwt'], function (err, user, info) {
if (err) {
if (err.oauthError) {
res
.status(400)
.json({ message: INVALID_TOKEN })
}
} else if (!user) {
res
.status(401)
.json({ message: UNAUTHORIZED })
} else {
req.user = user
next()
}
})(req, res, next);
}
Now I need to implement apple login. I tried using this library passport-apple
But I can not make it work. I am receiving the token from the app, send it to the back, but I only get
GET - /api/v1/shirts/?sorted%5BcreatedAt%5D=-1&filtered%5Bstate%5D=&pageNum=1&pageSize=10 - 302 - Found - 0b sent - 15 ms
I don't know if this is the correct approach. Should I get the user info from the app, send it to the backend and assign a JWT token to the created user? Or how can I do the same as I did with facebook?
After several try I find the solution thanks to this documentation https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
You need to send that in your body POST:
{
"grant_type": "authorization_code",
"code": "YOUR_CODE",
}
code:
"The authorization code received in an authorization response sent to your app. The code is single-use only and valid for five minutes. This parameter is required for authorization code validation requests." Apple Documentation
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?
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.
Using: passport-google-oauth2.
I want to use JWT with Google login - for that I need to disable session and somehow pass the user model back to client.
All the examples are using google callback that magically redirect to '/'.
How do I:
1. Disable session while using passport-google-oauth2.
2. res.send() user to client after google authentication.
Feel free to suggest alternatives if I'm not on the right direction.
Manage to overcome this with some insights:
1. disable session in express - just remove the middleware of the session
// app.use(session({secret: config.secret}))
2. when using Google authentication what actually happens is that there is a redirection to google login page and if login is successful it redirect you back with the url have you provided.
This actually mean that once google call your callback you cannot do res.send(token, user) - its simply does not work (anyone can elaborate why?). So you are force to do a redirect to the client by doing res.redirect("/").
But the whole purpose is to pass the token so you can also do res.redirect("/?token=" + token).
app.get( '/auth/google/callback',
passport.authenticate('google', {
//successRedirect: '/',
failureRedirect: '/'
, session: false
}),
function(req, res) {
var token = AuthService.encode(req.user);
res.redirect("/home?token=" + token);
});
But how the client will get the user entity?
So you can also pass the user in the same way but it didn't felt right for me (passing the whole user entity in the parameter list...).
So what I did is make the client use the token and retrieve the user.
function handleNewToken(token) {
if (!token)
return;
localStorageService.set('token', token);
// Fetch activeUser
$http.get("/api/authenticate/" + token)
.then(function (result) {
setActiveUser(result.data);
});
}
Which mean another http request - This make me think that maybe I didnt get right the token concept.
Feel free to enlighten me.
Initialize passport in index.js:
app.use(passport.initialize());
In your passport.js file:
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
'http://localhost:3000/auth/google/redirect',
},
async (accessToken, refreshToken, profile,
callback) => {
// Extract email from profile
const email = profile.emails![0].value;
if (!email) {
throw new BadRequestError('Login failed');
}
// Check if user already exist in database
const existingUser = await User.findOne({ email
});
if (existingUser) {
// Generate JWT
const jwt = jwt.sign(
{ id: existingUser.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update existing user
existingUser.token = jwt
await existingUser.save();
return callback(null, existingUser);
} else {
// Build a new User
const user = User.build({
email,
googleId: profile.id,
token?: undefined
});
// Generate JWT for new user
const jwt = jwt.sign(
{ id: user.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update new user
user.token = jwt;
await auth.save();
return callback(null, auth);
}
}));
Receive this JWT in route via req.user
app.get('/google/redirect', passport.authenticate('google',
{failureRedirect: '/api/relogin', session: false}), (req, res) => {
// Fetch JWT from req.user
const jwt = req.user.token;
req.session = {jwt}
// Successful authentication, redirect home
res.status(200).redirect('/home');
}