I want to connect to a remote server and use that for logins. This was not particularly hard.
Remote = DDP.connect('http://somesite.com');
Accounts.connection = Remote;
Meteor.users = new Mongo.Collection('users', Remote);
However, when I call meteor methods on my local code (there are multiple servers, but one login), it does not recognize the user.
Meteor.methods({
'start': function () {
if (!this.userId) {
// ...
} else {
throw new Meteor.Error(401, 'Unauthorized');
}
}
});
This always results in an error, despite being logged in.
How can I set my local user to the same user as the remote user?
Let's rename the following:
Remote => Login Server
local => Default Server This is where you call the Methods
I think you are better served by logging into Default Server, which then relays the login attempt to the Login Server.
This way you will be logged in on Default Server (if Login Server confirms the credentials are valid) when you use the Meteor.Methods that are on Default Server.
Accounts.validateLoginAttempt allows you to run arbitrary code in a callback on a LoginAttempt, allowing you to pass the validation from Default Server to Login Server:
if (Meteor.isServer) {
Accounts.validateLoginAttempt(function(attempt) {
//psuedocode block
var res = LoginServer_Login(attempt.methodArguements)
if (res === true) return true; // Login Success
else return false; // Login Failed
});
}
I'm not sure of the best way to implement LoginServer_Login func, though I'd try using HTTP.post to communicate with Login Server first (and recommend using restivus on the Login Server, it will give you authentication routes out of the box).
I just came across this package: admithub:shared-auth
It requires a shared db between the two meteor apps though.
Beyond this, you probably need to look into full SSO solutions.
Related
Assume you are working on a front end application that performs authentication through 3rd party api. Successful authentication returns a json web token.
What would be best practices to store such token and create some sort of session for user while he is active on the website i.e. didn't close a tab or browser, however refreshing / reloading a page should not destroy such session.
Also, how can this session be used to protect routes? I am working with a stack consisting of react / redux / node / express and quiet a few other libraries. I believe I can perform certain checks within my react-router, however wouldn't it be better to do these on the express side?
You can store the token in localStorage or sessionStorage, and include it in every API request.
Local storage outlives the tab, it's stored there until you explicitly delete from it, so refreshing a page won't be a problem. Even closing a tab and then coming back won't be.
Session storage allows you to store data. Page refreshes are fine, but tab closing isn't, which is closer to the behavior you want.
As for protecting routes, the server should obviously check the token on requests to all protected API routes.
On the browser side, you will probably want to show a login form if a user tries to visit a protected route but the token isn't there (or is invalid).
With react-router, you could do it like the official repo shows in the example, via onEnter hooks: https://github.com/reactjs/react-router/blob/master/examples/auth-flow/app.js
An alternative would be to create two top-level components, one for protected routes, one for public routes (like a landing page or the sign in/sign up forms). The protected handler will then in componentWillMount check if there's a token:
- PublicHandler
+ SignIn
+ SignUp
+ Index
- ProtectedHandler
+ Dashboard
+ MoneyWithdrawal
it may looks like that , with sessionStorage (JWT token is accesseble, untill browser or tab closed)
///action creator redux
export const signupUser = creds => dispatch =>{
dispatch(requestSignup());
return API.auth.signup(creds)
.then(res => {
sessionStorage.setItem('token', res.token);// <------------------
dispatch(receiveSignup(res));
return res;
})
.catch(err => {
dispatch(SignupError(err));
);
});
};
On client : handling auth through HOC redux-auth-wrapper
On server on server you can use passport-jwt strategy
passport.use('jwt',new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({where:{ id: jwt_payload.user.id }}).then(user=>{
if (user) {
done(null, jwt_payload.user);
} else {
done(null, false);
// or you could create a new account
}
},err=>{
console.log('Error ',err);
return done(err,false);
});
}));
then just add route handler
var checkJWT = passport.authenticate('jwt')
router.get('/protected',checkJWT, (req, res) =>{
res.json(req.user);
});
You don't need sessions on server for that
I want to completely dissociate my client app from Parse server, to ease the switch to other Baas/custom backend in the future. As such, all client request will point to a node.js server who will make the request to Parse on behalf of the user.
Client <--> Node.js Server <--> Parse Server
As such, I need the node.js server to be able to switch between users so I can keep the context of their authentification.
I know how to authentificate, then keep the sessionToken of the user, and I ve seen during my research than the "accepted" solution to this problem was to call Parse.User.disableUnsafeCurrentUser, then using Parse.User.become() to switch the current user to the one making a request.
But that feels hackish, and I m pretty sure it will, sooner or later, lead to a race condition where the current user is switched before the request is made to Parse.
Another solution I found was to not care about Parse.User, and use the masterKey to save everything by the server, but that would make the server responsible of the ACL.
Is there a way to make request from different user other than thoses two?
Any request to the backend (query.find(), object.save(), etc) takes an optional options parameter as the final argument. This lets you specify extra permissions levels, such as forcing the master key or using a specific session token.
If you have the session token, your server code can make a request on behalf of that user, preserving ACL permissions.
Let's assume you have a table of Item objects, where we rely on ACLs to ensure that a user can only retrieve his own Items. The following code would use an explicit session token and only return the Items the user can see:
// fetch items visible to the user associate with `token`
fetchItems(token) {
new Parse.Query('Item')
.find({ sessionToken: token })
.then((results) => {
// do something with the items
});
}
become() was really designed for the Parse Cloud Code environment, where each request lives in a sandbox, and you can rely on a global current user for each request. It doesn't really make sense in a Node.js app, and we'll probably deprecate it.
I recently wrote a NodeJS application and had the same problem. I found that the combination of Parse.User.disableUnsafeCurrentUser and Parse.User.become() was not only hackish, but also caused several other problems I wasn't able to anticipate.
So here's what I did: I used
Parse.Cloud.useMasterKey(); and then loaded the current user by session ID as if it was a regular user object. It looked something like this:
module.exports = function(req, res, next) {
var Parse = req.app.locals.parse, query;
res.locals.parse = Parse;
if (req.session.userid === undefined) {
res.locals.user = undefined;
return next();
}
Parse.Cloud.useMasterKey();
query = new Parse.Query(Parse.User);
query.equalTo("objectId", req.session.userid);
query.first().then(function(result) {
res.locals.user = result;
return next();
}, function(err) {
res.locals.user = undefined;
console.error("error recovering user " + req.session.userid);
return next();
});
};
This code can obviously be optimized, but you can see the general idea. Upside: It works! Downside: No more use of Parse.User.current(), and the need to take special care in the backend that no conditions occur where someone overwrites data without permission.
I am following a meteor tutorial on eventedmind. We put the todos collection information in lib/collections/todos.js. The app was generated with iron.
When I load the app in the browser I can plainly see the folder under sources. It looks like:
Todos = new Mongo.Collection('todos');
// if server define security rules
// server code and code inside methods are not affected by allow and deny
// these rules only apply when insert, update, and remove are called from untrusted client code
if (Meteor.isServer) {
// first argument is id of logged in user. (null if not logged in)
Todos.allow({
// can do anythin if you own the document
insert: function (userId, doc) {
return userId === doc.userId;
},
update: function (userId, doc, fieldNames, modifier) {
return userId === doc.userId;
},
remove: function (userId, doc) {
return userId === doc.userId;
}
});
// The deny method lets you selectively override your allow rules
// every deny callback must return false for the database change to happen
Todos.deny({
insert: function (userId, doc) {
return false;
},
update: function (userId, doc, fieldNames, modifier) {
return false;
},
remove: function (userId, doc) {
return false;
}
});
}
My question is does this propose a security threat? If a javascript file is stored in the lib directory can it be hijacked by the client?
Never ever ever ever use Meteor.isServer. Instead put your server methods under /server. Code there is not served up to the client.
I disagree with redress that moving inserts to the server is more secure than doing inserts on the client. After all, a logged-in user can simply open the console and type Meteor.call('addPost', new_post_fields) (after having inspected a document in postsCollection to reverse engineer the schema) and the server will happily execute that. The cost of doing the insert via a method call is that you lose latency compensation, one of the major benefits of Meteor. Your application will feel laggy because your inserts all require a server round-trip to reflect in the UI.
Let me be more specific in response to redress' comments:
If your method code is in /lib it will be visible to both the client and the server and will run once in each place, with latency compensation. It can be modified on the client but not on the server. But it can be seen.
If your method code is in /server it will only be visible to the server and will run without latency compensation.
Meteor.call() can be invoked from the console in the client with any arguments. Your method code needs to protect against such attacks.
I recommend using a combination of allow/deny rules on the server along with aldeed:simple-schema at a minimum to control what goes into your collections.
It seems that in Meteor, we cannot call a server side route to render a file to the page without some sort of work-around from our normal workflow in terms of authentication.
Software/Versions
I'm using the latest Iron Router 1.* and Meteor 1.* and to begin, I'm just using accounts-password.
Background/Context
I have an onBeforeAction that simply redirects the user to either the welcome page or home page base upon if the user is logged in or not:
both/routes.js
if (Meteor.isClient) {
Router.onBeforeAction(function () {
if (!Meteor.user() || Meteor.loggingIn())
this.redirect('welcome.view');
else
this.next();
}
,{except: 'welcome.view'}
);
Router.onBeforeAction(function () {
if (Meteor.user())
this.redirect('home.view');
else
this.next();
}
,{only: 'welcome.view'}
);
}
In the same file, both/routes.js, I have a simple server side route that renders a pdf to the screen:
Router.route('/pdf-server', function() {
var filePath = process.env.PWD + "/server/.files/users/test.pdf";
console.log(filePath);
var fs = Npm.require('fs');
var data = fs.readFileSync(filePath);
this.response.write(data);
this.response.end();
}, {where: 'server'});
Cookie related code added
I found a SO answer where a method to set and get the cookies is outlined here: SO Cookies technique
And then added the following code to my project:
client/main.js
Deps.autorun(function() {
if(Accounts.loginServicesConfigured() && Meteor.userId()) {
setCookie("meteor_userid",Meteor.userId(),30);
setCookie("meteor_logintoken",localStorage.getItem("Meteor.loginToken"),30);
}
});
But this does not work as expected, the setCookie code is not valid for some reason.
My Questions
Question 1: Using cookies, how do I properly set/get and check for the cookies?
Question 2: What precautions should I take using the cookies approach?
Side Question: Is cookies the best way to go about this, or is there a
simpler way to achieve the same thing?
You can call a method that generates a temporary access token and when you get a positive answer from the server (meaning that the token was created), you can check the validity of this token before loading the PDF.
As for using cookies: It might be more convenient when compared to calling a method, waiting and then redirect/open a new tab. I tested your code, with the W3C client side javascript functions, and it worked at client side. The function getCookie assume that you're the client, not the server. You need to check the request and extract the cookies.
I'm creating an ST2 application where you can login/register etc.
I'm wondering what the normal way is of logging in and having the User state across the entire application.
I have a User model with a REST proxy to get/save the data. When you load up the application I'm doing this to grab the user:
launch: function () {
var User = Ext.ModelManager.getModel('App.model.User');
User.load("", {
success: function (user) {
// I have the user here but it's only within this scope
}
});
}
But doing this it's only available within this function... so what do people usually do to get ahold of the user across the whole application? just store it within the application like:
application.user = user;
or do you create a store with an ID of User, using the User model and then retrieve with:
launch: function () {
var User = Ext.StoreManager.get('User');
User.load(function(user) {
// Do application logged in stuff
self.getApplication().fireEvent('userLogsIn');
});
}
someRandomFunction: function () {
var user = Ext.StoreManager.get('User').getAt(0),
email = user.get('email');
console.log(email);
}
Thanks, Dominic
Generally speaking you cannot rely on any information you save locally in your JS application. It can be spoofed and altered relatively easy.
What you need to do is to send username/password combination to your server back end. Server then should return encrypted cookie which application will send back to the server with each following request. Only by decrypting and verifying this cookie server can be sure of identity of logged in user.