While trying to implement the passport-same authentication methods I have hit a roadblock. The callback function passed to the passport.authenticated function does not get called.
router.post("/saml/callback",
function (req, res, next) {
req.body.SAMLResponse = req.body.SAMLResponse.replace(/[\n\r]/g, "");
next();
},
function (req, res, next) {
console.log("Calling passport handler");
console.log(req.body);
try {
const response = passport.authenticate("saml",
{
failureRedirect: "/saml/error",
failureFlash: true
}, (error, user, info) => {
console.log(error, user, info);
next();
})(req, res, next);
console.log(response);
} catch(e) {
console.log(e);
}
console.log("Line after passport handler");
},
function (req, res) {
res.redirect("/saml/success");
}
);
My express app hangs when entering this method but only with 1 specific saml provider (using https://samltest.id as test provider DOES work with the exact same code). It seems that an error occurs in this authenticate method but I cannot for the live of me find this error.
How do I get the error in this callback.
Log output:
Calling passport handler
{SAMLResponse: 'base64encoded saml response'}
undefined
Line after passport handler
Error: connect ETIMEDOUT ip:443
The problem turned out to not at all be in this piece of code but instead in the initiation of the saml provider.
I have a callback function in Strategy({cert: function(callback)....}); which tries to fetch the signing certificates for the saml response. The IDP was however not accessible from my SP due to it being a test server, therefor the callback never got called.
TL;DR; If you are using the cert key in your Strategy definition; and said key utilises a callback, check if that callback gets called!
Related
I think I have resolved this issue in the process of writing it, basically the solution seems to be:
Move the static file handler above the other instance of use()
Confirmation that this is an acceptable approach would be appreciated though and perhaps help others in a similar scenario.
Desired Behaviour
Apply a use() instance to all routes except those handled by:
app.use(express.static("dist"));
Actual Behaviour
use() is being applied to all routes, including those handled by:
app.use(express.static("dist"));
Scenario
For securing access to API's, I am using the model described in this Lynda.com tutorial:
Node.js: Securing RESTful APIs
In pseudo code, the model is essentially comprised of:
a global use() instance that checks if a jwt token has been sent
if a token has been sent, if verifies the token
it sets the req.user property to undefined if verification fails or a token wasn't sent
otherwise, it sets the req.user property to the decoded jwt value if verification succeeds
subsequent middleware performs conditional behaviour based on the value of req.user
This model is working well for all intents and purposes.
However, I recently added some console logging and can see that verification is being performed for both:
api requests (desired behaviour)
static files served via app.use(express.static("dist")) per this convention (undesired behaviour)
Question
How can I apply the verification use() instance to all routes, except those handled by app.use(express.static("dist")).
What I've Tried
I think I have resolved this issue by moving section 2 of the code below above section 1.
// 01. verification use() called on all requests
app.use((req, res, next) => {
// if jwt authorisation has been sent in headers, verify it
if (req.headers && req.headers.authorization && req.headers.authorization.split(' ')[0] === 'JWT') {
console.log("jwt verification sent, verifying...");
try {
// this is synchronous as it has no callback
req.user = jsonwebtoken.verify(req.headers.authorization.split(' ')[1], 'RESTFULAPIs');
console.log("jwt verified, will return decoded value");
} catch (err) {
req.user = undefined;
console.log("jwt verification failed, user will remain undefined: " + err);
}
// move to the next piece of middleware
next();
}
// if jwt authorisation has not been sent in headers
else {
console.log("jwt verification not sent, leaving user as undefined");
console.log(req.originalUrl);
req.user = undefined;
// move to the next piece of middleware
next();
}
});
// 02. use() for serving static files
app.use(express.static("dist"));
// 03. middleware to check if login has been verified
const api_login_required = (req, res, next) => {
// if token verification was successful and the user property exists
if (req.user) {
// move to the next piece of middleware
next();
}
// otherwise, return unauthorised user message
else {
res.json({ verification: 0 });
}
}
// 04. middleware called in route handlers
app.route("/api/:api_version/users/private_data")
.get(api_login_required, api_users_private_data_get)
.post(api_login_required, api_users_private_data_post);
Middleware always controls the flow from to button in which order they wrote. Like
if (example 1)code like
app.use((req,res, next)=>{// middleware 1; next()} )
app.get('/rot1', (req, res)=> res.status(200).send('route 1'));
app.get('/rot2', (req, res)=> res.status(200).send('route 2'));
In this case, middleware appears in both route1, route because of middleware set at the top of the route.
If (example 2)code like
app.use((req,res, next)=>{// middleware 1; next()} )
app.get('/rot1', (req, res)=> res.status(200).send('route 1'));
app.use((req,res, next)=>{// middleware 2; next()} )
app.get('/rot2', (req, res)=> res.status(200).send('route 2'));
Here middleware1 applied in both route1 and route 2
But middleware2 applied only on route2.
But you can also define specific middleware for each route
function middleware1(req, res, next){
next();
}
function middleware2(req, res, next){
next();
}
app.get('/rot1', middleware1, (req, res)=> res.status(200).send('route 1'));
app.get('/rot2', middleware2, (req, res)=> res.status(200).send('route 2'));
Here middleware1 only applied on route1 and middleware2 only applied on route2.
Maybe above explanation help you!!
I'm somewhat new to NodeJS, and current I used Express and Request ( https://github.com/request/request ) to forward my app request to REST api server, current my code shown below:
app.use('/rest/*', function(req, res) {
req.pipe(request('http://ipaddress/api')).pipe(res);
});
this code works when the REST API server is OK, but if the rest api server goes down, my nodejs app also goes down, because request stream will fail and the error is not caught by my app.
I checked the Request github page, it provides one way to handle the stream error, like
app.use('/rest/*', function(req, res) {
req.pipe(request('http://ipaddress/api').on('error', function(err) {
console.log(err);
})).pipe(res);
});
this can only log the error and prevent my NodeJS app crashing, but I want to change the response when error occurred so that the changed response can be piped to final one, for example, what I want to do in pseudocode:
app.use('/rest/*', function(req, res) {
req.pipe(request('http://ipaddress/api').on('error', function(err) {
console.log(err);
// what I want to do in pseudocode
response.statusCode = 500;
response.json = {
reason: err.errno
};
})).pipe(res);
});
Are there any ways to solve my problems? Thanks for any ideas!
Untested but could you pass the error back to middleware to handle the reponse?
app.use('/rest/*', function(req, res, next) {
req.pipe(request('http://ipaddress/api').on('error', function(err) {
return next(err)
})).pipe(res);
});
Handled like so
// Exception handling
app.use(function (error, req, res, next) {
console.log(error);
res.status(500).send(JSON.stringify(error));
next();
});
I'm building an API with Node.js, and I have some endpoints I want to secure.
For simplicity let's assume I'm using HTTP basic authentication (passport-http) for all of my endpoints.
What I'd like to do on top of that, is to make sure that a route like this: api.example.com/users/:uid/ is only accessible by a user with that ID.
I can do it with something like this:
app.get('/users/:uid',
passport.authenticate('basic', {
session: false
}),
function (req, res, next) {
if (req.params.uid !== user.id) {
return next(new Error('Unauthorized'));
}
return next();
},
function (req, res, next) {
// do secret stuff
}
);
But I wonder if there's a way to do this without adding additional middleware, by using Passport itself:
app.get('/users/:uid',
passport.authenticate( ??? ),
function (req, res, next) {
// do secret stuff
}
);
Is it possible? If not, is there a better way?
You can try something perhaps like this. General description: authenticate all requests that hit anything under the /users route as requiring authentication. On your specific route, use some middleware that makes sure that the user trying to access the specific route is the one in the route itself via that uid.
function authorizeUser(req, res, next) {
if (req.user.uid !== req.params.uid) next(new Error('Not your profile!'));
next();
}
// Require login for entire /users section
app.use('/users', passport.authenticate('basic', { session: false }));
// Authorize /users/:uid section to one user
app.use('/users/:uid', authorizeUser);
// Nested routes will all be secured by the middleware above.
app.get('/users/:uid', function (req, res) {
// Secret stuff
});
app.get('/users/:uid/foo/bar', function (req, res) {
// Also secret
});
If you're only securing one endpoint, you can just put it all on the same route.
The idea is the follow:
Send the login directory when the user is not authenticated.
Send the app directory one time that the user logs in (in this case, using passport module).
Example:
Not logged:
request: GET /
response: index.html from PATH_login
Logged:
request: GET /
response: index.html from PATH_app
I tried this but it didn't work:
app.use(function(req,res,next){
if ( req.isAuthenticated() )
{
// user is authenticated
return express.static(PATH_app)
}
else
{
// user is not authenticated
return express.static(PATH_login)
}
});
On initialization, you're setting that the middleware function that does the switching should be called for every request.
You should also initialize each of the middleware functions that would be switched between at this time.
At runtime for each request (when the code in the function you pass to app.use gets run), for that switching function to forward to the appropriate middleware, it would call the relevant function:
var appStatic = express.static(PATH_app);
var loginStatic = express.static(PATH_login);
app.use(function(req, res, next) {
if (req.isAuthenticated()) {
// user is authenticated
return appStatic(req, res, next);
} else {
// user is not authenticated
return loginStatic(req, res, next);
}
});
I'm using Express and Passport OpenID Google strategy and I would like to set returnURL on each auth request to be able to return to the page that initiated that auth.
The situation is that I have HTML5 slides application with Node.js backend (and with social stuff and editor and Portal and extensions... https://github.com/bubersson/humla) and I want be able to log in user on some slide (via slide menu...) but then I want him to get back to same slide easily.
So I would need something like this?
app.get('/auth/google', function(req,res) {
var cust = "http://localhost:1338/"+req.params.xxx;
passport.authenticate('google', returnURL:cust, function ...
}
I've read Passport's guide, but still don't know how to do that. I know this wouldn't be safe, but how else could I do it?
Or how can I make the application to return to the page from where the login has been initiated? Or is there a way to make OpenID authentication using AJAX (and still be able to use passport as well)?
I've figured this out for my apps Twitter authentication, I am sure that the GoogleStrategy is quite similar. Try a variant of this:
Assuming you have defined the route for the callback from the authentication service like so (from the passport guide):
app.get('/auth/twitter/callback',
passport.authenticate('twitter', {
successRedirect: authenticationRedirect(req, '/account')
, failureRedirect: '/'
})
);
Just change that block to this:
app.get('/auth/twitter/callback', function(req, res, next){
passport.authenticate('twitter', function(err, user, info){
// This is the default destination upon successful login.
var redirectUrl = '/account';
if (err) { return next(err); }
if (!user) { return res.redirect('/'); }
// If we have previously stored a redirectUrl, use that,
// otherwise, use the default.
if (req.session.redirectUrl) {
redirectUrl = req.session.redirectUrl;
req.session.redirectUrl = null;
}
req.logIn(user, function(err){
if (err) { return next(err); }
});
res.redirect(redirectUrl);
})(req, res, next);
});
Now, define your middleware for authenticated routes to store the original URL in the session like this:
ensureAuthenticated = function (req, res, next) {
if (req.isAuthenticated()) { return next(); }
// If the user is not authenticated, then we will start the authentication
// process. Before we do, let's store this originally requested URL in the
// session so we know where to return the user later.
req.session.redirectUrl = req.url;
// Resume normal authentication...
logger.info('User is not authenticated.');
req.flash("warn", "You must be logged-in to do that.");
res.redirect('/');
}
Works!
Wherever you have your login button, append the request's current URL as a
query parameter (adjust for whatever templating system you use):
<a href='/auth/google?redirect=<%= req.url %>'>Log In</a>
Then, add middleware to your GET /auth/google handler that stores this value in
req.session:
app.get('/auth/google', function(req, res, next) {
req.session.redirect = req.query.redirect;
next();
}, passport.authenticate('google'));
Finally, in your callback handler, redirect to the URL stored in the session:
app.get('/auth/google/callback', passport.authenticate('google',
failureRedirect: '/'
), function (req, res) {
res.redirect(req.session.redirect || '/');
delete req.session.redirect;
});
Try res.redirect('back'); in the callback for passport.authenticate
According to the author this isn't possible with OpenID strategies. We managed to update these dynamically by directly accessing the variables:
app.get('/auth/google', function(req, res, next) {
passport._strategies['google']._relyingParty.returnUrl = 'http://localhost:3000/test';
passport._strategies['google']._relyingParty.realm = 'http://localhost:3000';
passport.authenticate('google')(req, res, next);
});