Meaning, in my application, I want to check if a customId is present in the client request. If yes, I will proceed authentication using that with my custom logic.
If customId is not present, I want to use passport-jwt authentication.
passport registers its initialize method at server startup itself.
My specific question is how do I use passport.authenticate only if customId is not present.
Any help is much appreciated.
Yeah you can, it's all just middleware! Here's an example of how you would do it, I've not run this code so it may not build but it shows how to do what you're after.
const express = require('express');
const passport = require('passport');
const passportJWT = require('passport-jwt');
// My express application where I setup routers, middleware etc
const app = express();
// Initialise passport
app.use(passport.initialize());
// Setup my passport JWT strategy, this just setups the strategy it doesn't mount any middleware
passport.use(new passportJWT.Strategy({
secretOrKey: '',
issuer: '',
audience: '',
}, (req, payload, done) => {
doSomeFancyAuthThingy(payload, (err, user) => {
done(err, user);
});
}));
// Now create some middleware for the authentication
app.use((req, res, next) => {
// Look to see if the request body has a customerId, in reality
// you probably want to check this on some sort of cookie or something
if (req.body.customerId) {
// We have a customerId so just let them through!
next();
} else {
// No customerId so lets run the passport JWT strategy
passport.authenticate('jwt', (err, user, info) => {
if (err) {
// The JWT failed to validate for some reason
return next(err);
}
// The JWT strategy validated just fine and returned a user, set that
// on req.user and let the user through!
req.user = user;
next();
});
}
});
As you can see the main thing you're looking for is where we create the middleware. Here we just create our own middleware and run a check (the if statement), if it fails then we run passport.authenticate which triggers the strategy we created on the passport.use block.
That will allow you to conditionally do any sort of authentication with Passport!
Related
I am struggling with getting Google OAuth to work with my Express/React application whilst using Passport.js. I am using JWTs, not sessions.
In my React webapp client, I have a "login with Google" button that calls my backend API /auth/google/ with the following route setup in Express:
router.get('auth/google', passport.authenticate('google', {session: false, scope: ['email','profile']}) );
My Passport.js google strategy is:
const googleStrategy = new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/api/v1/auth/google/callback",
passReqToCallback : true
},
async (request, accessToken, refreshToken, profile, done) => {
try {
console.log('profile', profile);// ** CORRECT USER PRINTED **
let existingUser = await User.findOne({ 'google.id': profile.id });
// if user exists return the user
if (existingUser) {
console.log('Found existing user...');
return done(null, existingUser);
}
// if user does not exist create a new user
const newUser = new User({
method: 'google',
googleId: profile.id,
profileImage: profile.photos[0].value,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
shortName: profile.displayName,
});
await newUser.save();
return done(null, newUser);
} catch (error) {
return done(error, false)
}
}
);
My Google developer dashboard is setup to call the following URL in my Express API backend upon successful authentication: /auth/google/callback
My Express route for this is defined as: router.get('auth/google/callback', passport.authenticate('google', {session: false}), authController.googleAuthCallback);
My Express googleAuthCallback function is defined as:
exports.googleAuthCallback = async (req, res) => {
console.log(req.user) // ** WRONG USER PRINTED HERE ** different from above user printed in google strategy
}
The strange this is when I console.log the profile variable in my googleStrategy, I get the right user profile information for the account from Google. This means the authentication vis a vis Google is fine. However, this same account is NOT being provided to my /auth/google/callback endpoint in the req.user object at that location. It is an entirely different account (it is the first value from my database of Users, which is authenticated using local authentication).
How do I get the user object back to my Express callback endpoint that I supplied to Google in the developer console as the authorized redirect URI?
As a general question, what happens after the strategy calls return done(null, existingUser);? I have no callback in the /auth/google route after the passport.authenticate() middleware is called so what happens next?
I am using "passport-google-oauth20": "^2.0.0"
My let existingUser = await User.findOne({ 'google.id': profile.id });
line was incorrect and was essentially returning no user. Mongoose does not complain and hence the strategy was just returning the first user from my database rather than the authenticated google user.
I have a small ExpressJS server with a login feature. Some pages are secured with self-written authenticate middleware that checks if your Json WebToken is correct.
I read that just one Json WebToken isn't secure enough and that you need a refresh token as well. So I added a Refresh Token. This is all verified on the server.
Now when the token is expired, I check if the user has a refreshToken and if so I create a new token and set it as a cookie. Like this:
const jwt = require('jsonwebtoken');
// Simple array holding the issued refreshTokens
const { refreshTokens } = require('../lib/auth.js');
module.exports.authenticateToken = function(req, res, next) {
const token = req.cookies.token;
const refreshToken = req.cookies.refreshToken;
if(token === null) return res.redirect('/login');
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
if(verified) return next();
} catch(err) {
try {
const refreshInDb = refreshTokens.find(token => token === refreshToken);
const refreshVerified = refreshInDb && jwt.verify(refreshToken, process.env.REFRESHTOKEN_SECRET);
const newToken = jwt.sign({ email: refreshVerified.email }, process.env.TOKEN_SECRET, { expiresIn: 20 });
res.cookie('token', newToken, { maxAge: 900000, httpOnly: true });
return next();
} catch(err) {
return res.redirect('/login');
}
}
};
Now is this code correct & secure enough for a small webapplication? Am I missing stuff? It feels so... easy?
Seems like you are veryfing both tokens at the same endpoint. This approach is wrong.
In your login endpoint, validate the user and password against database. If credentials are correct we respond with an access token and a refresh token
router.post('/login',
asyncWrap(async (req, res, next) => {
const { username, password } = req.body
await validateUser(username, password)
return res.json({
access_token: generateAccessToken(), // expires in 1 hour
refresh_token: generateRefreshToken(), // expires in 1 month
})
})
)
In your authenticated routes, you should validate only the access token (the user should send ONLY this one)
// middleware to validate the access token
export const validateToken = asyncWrap(async (req, res, next) => {
const data = await verifyAccessToken(req.headers.authorization)
req.auth = data
next()
})
If the access token expires, the user should refresh its token. In this endpoint we will validate the refresh token and respond with two new tokens:
router.post('/refresh',
asyncWrap(async (req, res, next) => {
const { refresh_token } = req.body
await verifyRefreshToken(refresh_token)
return res.json({
access_token: generateAccessToken(), // expires in 1 hour
refresh_token: generateRefreshToken(), // expires in 1 month
})
})
)
I read that just one Json WebToken isn't secure enough and that you need a refresh token as well. So I added a Refresh Token. This is all verified on the server.
Using refresh tokens has nothing to do with security of the JWT or access token. Refresh tokens are just a UX feature. They allow you to get new access tokens without asking the user to authorize again. Having a refresh token in your app doesn't automatically make it more secure.
Now when the token is expired, I check if the user has a refreshToken and if so I create a new token and set it as a cookie. Like this:
When implemented this way the refresh token doesn't grant any more security to your application. You could as well keep the access tokens in the db and refresh them when they are expired.
Are you sure that you need JWTs at all? It looks like you're using them as you would use a session based on cookies. It should be simpler to deal with sessions. You are using http-only cookies for your tokens so you already use it pretty much like a session.
Now is this code correct & secure enough for a small webapplication?
Secure enough is a concept that depends on the data that your application has access to. If it's nothing sensitive, and you know that your app can't really be abused by an attacker, then it is fine to have only some basic security in place.
I have just started with passport.js. From this article, I got what is the flow of all the passport methods and implemented the same in my application and it is working. Here is my server.js and I am using passport-local strategy. Angular app and rest APIs on the same server
import { registerControllersFromFolder } from 'giuseppe';
import { MessageManager } from './messaging/MessageManager';
import express = require('express');
import bodyParser = require('body-parser');
import session = require("express-session");
import http = require('http');
// class to hold user info
class User {
userId: number;
userName: string;
constructor(userId: number, userName: string) {
this.userId = userId;
this.userName = userName;
}
}
// server class to create http server
export class Server {
// list of apis for which authentication is not required
private static publicApiList: string[] = ["/services/login", "/login", "/favicon.ico"];
// request interceptor that will check user authentication
private static isAuthenticated = (req, res, next) => {
console.log("Authenticating :", req.originalUrl);
if (req.isAuthenticated() || Server.publicApiList.indexOf(req.originalUrl) > -1) {
// express routing
if (req.originalUrl.startsWith("/services")) {
console.log("Express Routing");
return next();
} else { // angular routing -> return index.html
console.log("Angular Routing");
return res.sendFile(__dirname + "/public/index.html");
}
} else {
console.log("User not authenticated.")
res.redirect('/');
}
};
static startServer() {
let userList: User[] = [new User(1, "Sunil"), new User(2, "Sukhi")];
let app = express();
// passport library
let passport = require('passport');
let LocalStrategy = require('passport-local').Strategy;
// middlewares
app.use(express.static(__dirname + "/public"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ resave: false, saveUninitialized: true, secret: "secretKey123!!" }));
// passport middleware invoked on every request to ensure session contains passport.user object
app.use(passport.initialize());
// load seriliazed session user object to req.user
app.use(passport.session());
// Only during the authentication to specify what user information should be stored in the session.
passport.serializeUser(function (user, done) {
console.log("Serializer : ", user);
done(null, user);
});
// Invoked on every request by passport.session
passport.deserializeUser(function (user, done) {
let validUser = userList.filter(user => user.userId === user.userId)[0];
console.log("D-serializer : ", validUser);
done(null,validUser);
});
// passport strategy : Only invoked on the route which uses the passport.authenticate middleware.
passport.use(new LocalStrategy({
usernameField: 'name',
passwordField: 'password'
},
function (username, password, done) {
console.log("Strategy : Authenticating if user is valid :", username)
let user = userList.filter(user => username === user.userName);
console.log("Valid user : ", user)
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
return done(null, user[0]);
}
));
// intercept request for authentication
app.use(Server.isAuthenticated);
app.post('/services/login', passport.authenticate('local', {
successRedirect: '/profile',
failureRedirect: '/login'
}));
app.get('/services/logout', (req: any, res: any) => {
req.logout();
console.log("User Logout");
res.send("{status:'logout'}")
});
// http server creation
let server = http.createServer(app);
registerControllersFromFolder({ folderPath: './api' })
.then(router => {
app.use(router);
/* start express server */
})
.catch(err => {
/* error happened during loading and registering */
});
server.listen(7000, () => {
console.log('Up and running on port 7000');
});
}
}
exports.startServer = Server.startServer;
// Call a module's exported functions directly from the command line.
require('make-runnable');
When I hit localhost:7000 it serves the index.html page as I have used
app.use(express.static(__dirname + "/public"));
and this is an angular app and because of angular routing login module will get loaded by default. I have used a middleware that checks request authentication and if true then based on request prefix (angular or express) routing is done.
For the login request defined local strategy method is called and if this is true it calls serializer method that takes the responsibility which data should be stored in the request session. and then sucessRedirect or failureRedirect is called.
For subsequent request, As I have used middleware that checks if req.isAuthenticated is true if so then request is served otherwise the user is redirected to login page. I know in every subsequent request deserializeUser method is called that contains the object that was stored by serializeUser method in the login request. As per the document, this makes a call to the database to check valid user.
But I am confused but is the actual use case of deserializeUser method? Where can I take the benefit of this method and if I am intercepting ecah request and check req.isAuthenticted() then why to call database in deserializeUser method?>
As stated in this answer
The first argument of deserializeUser corresponds to the key of the
user object that was given to the done function (see 1.). So your
whole object is retrieved with help of that key. That key here is the
user id (key can be any key of the user object i.e. name,email etc).
In deserializeUser that key is matched with the in memory array /
database or any data resource.
The fetched object is attached to the request object as req.user
Thus, the benefit of deserializeUser is that you have the user object available on every request thereafter.
You ask why you need to use deserializeUser if you call req.isAuthenticated, and the answer lies in the implementation of req.isAuthenticated:
req.isAuthenticated = function() {
var property = 'user';
if (this._passport && this._passport.instance) {
property = this._passport.instance._userProperty || 'user';
}
return (this[property]) ? true : false;
};
To me, it looks like req.isAuthenticated is looking for req[user] to be set, and thus, deserializeUser must be called before it can work.
I would like to reproduce how plunker manages the anonymous accounts.
Plunker can recognise an anonymous user. For example, we can save a plunker as anonym and then freeze it. As a result,
only the same user (before clearing browser history) has the full access to this plunker (eg, save a modification, unfreeze).
if the same user opens it in another browser or other users open the same link, they can NOT save any modification; they have to fork it.
In my website, I use the local strategy of passport.js to manage named users. For example,
router.post('/login', function (req, res, next) {
if (!req.body.username || !req.body.password)
return res.status(400).json({ message: 'Please fill out all fields' });
passport.authenticate('local', function (err, user, info) {
if (err) return next(err);
if (user) res.json({ token: user.generateJWT() });
else return res.status(401).json(info);
})(req, res, next);
});
And I use a localStorage to store the token. For example,
auth.logIn = function (user) {
return $http.post('/login', user).success(function (token) {
$window.localStorage['account-token'] = token;
})
};
auth.logOut = function () {
$window.localStorage.removeItem('account-token');
};
Does anyone know if passport.js has any strategy or existing tools to manage the anonymous account like what plunker does? Otherwise, is there a conventional way to achieve this?
Passport allows anonymous auth. There is a passport anonymous strategy for the same:
app.get('/',
// Authenticate using HTTP Basic credentials, with session support disabled,
// and allow anonymous requests.
passport.authenticate(['basic', 'anonymous'], { session: false }),
function(req, res){
if (req.user) {
res.json({ username: req.user.username, email: req.user.email });
} else {
res.json({ anonymous: true });
}
});
This uses your basic strategy in place, you can substitute that with a local strategy if you're using local authentication. It falls back to an anonymous strategy in case nothing is supplied, as can be seen here:
passport.use(new BasicStrategy({
},
function(username, password, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// Find the user by username. If there is no user with the given
// username, or the password is not correct, set the user to `false` to
// indicate failure. Otherwise, return the authenticated `user`.
findByUsername(username, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (user.password != password) { return done(null, false); }
return done(null, user);
})
});
}
));
// Use the BasicStrategy within Passport.
// This is used as a fallback in requests that prefer authentication, but
// support unauthenticated clients.
passport.use(new AnonymousStrategy());
The full example may be found here:- https://github.com/jaredhanson/passport-anonymous/blob/master/examples/basic/app.js
Remember cookies with a longer expiration date is how anonymous user is identified. This goes the same way as any server side technology trying to authenticate user by username and password and then just sets a cookie for the http request.
I am using KeystoneJS and wanna insert the oauth2 codes.
All my Token and Users codes are run very fine; the methods are triggered via console.log. The token is generated successfully.
However, there is no return and finally I receive a timeout in my client:
// Setup Route Bindings
exports = module.exports = function(app) {
// Oauth2
var oauth = oauthserver({
model: require('../models/Client'),
grants: ['password', 'refresh_token'],
debug: true });
// Views
app.get('/', routes.views.index);
// both routes below can be triggered correctly, but timeout return
app.all('/oauth/token', oauth.grant());
app.get('/secret', oauth.authorise(), function (req, res) {
res.send('Secret area');
});
};