Given two middleware, how can you add something that would get hit first, then determine which of the middleware to pass the request onto. The request can only be dropped or forwarded to one of the middleware, never both.
app.use( function *(next) {
if (isAuthenticated)
*private middleware*
else
*use public middleware*
});
More Specifically:
I have a homepage / that should have a variable response depending on if the user is logged in or not (using token-based auth here). For simplicity, if the request user does not have a token, then public.html is the response while private.html for request bearing a valid token.
Now this would be easy enough to just implement within one function, but I'd like to have separated routers, figure that would keep the code more manageable.
So I need to somehow be able to choose which middleware router the request goes to right? No idea how to do that.
var publicR = new router();
publicR.get('/', function *(next) {
....public.html....
});
var privateR = new router();
privateR.get('/', function *(next) {
....private.html....
});
app.use(function(){
if(isAuthenticated)
...use privateR.routes();
else
...use publicR.routes();
});
First off, it's unusual and really not a good idea to present entirely different content for a given URL when logged in or not logged in. So, trying to have two different routers that all serve the same routes, but one is for a logged in user and the other for a not-logged in user is probably just not a good design idea.
The more usual case is to have a portion of a page that might be different when logged in. In that case, you have a single route creating the page that handles doing something slightly different with the content when logged in or not.
If you really want to have completely different content and behavior when logged in, then you should probably redirect to a different URL when logged in. In that case, you can just use an entirely different router for the "logged in" URLs. This will also work better for search engines since a given URL will report consistent content and not be different depending upon the state of the user. This also makes the use of Routers in Express really easy. Your "logged in" router serves the logged in URLs. You can have middleware that checks a logged-in URL to see if you are actually logged in and, if not, redirects back to the non-logged in page and vice versa.
In case anyone else runs into this issue, this is working for me:
var publicR = new router();
publicR.get('/', function *(next) {
....public.html....
});
var privateR = new router();
privateR.get('/', function *(next) {
....private.html....
});
app.use(mySwitcher);
function *mySwitcher(next){
if(isAuthenticated)
yield privateR.routes().call(this,next);
else
yield publicR.routes().call(this,next);
}
Related
I'm struggling to initialize my Vuex store with the account details of the logged in user from localStorage.
I've been through as many examples as I can of Auth using Nuxt, and none of them demonstrate how on the client side to pull an authToken from localStorage to re-use with subsequent API requests when the user refreshes the page or navigates to the App
Disclaimer: I'm not using Nuxt in SSR (this might affect your answer).
What is annoying is that I can actually load from localStorage and initialize my state but then it gets overwritten. I'll show you what I mean with this small code example:
buildInitialState () {
if (!process.server) {
// Query the localStorage and return accessToken, refreshToken and account details
return {accessToken: <valid-string>, refreshToken: <valid-string>, account: <valid-json-blob>}
} else {
// Return "empty map", we use the string "INVALID" for demonstration purposes.
return {accessToken: "INVALID", refreshToken: "INVALID", account: "INVALID"}
}
}
export const state = () => buildInitialState()
If I put a breakpoint on buildInitialState I can see that I correctly initialize the state with the value of localStorage, i.e. I get the accessToken and refreshToken, etc.. back.
All seems well.
But then .....in another part of the program I'm using Axois to send requests, and I use an axios interceptor to decorate the request with the accessToken. To do this I have to stick it into a plugin to get access to the store.
Something like so:
export default ({ app, store }) => {
axios.interceptors.request.use((config) => {
const accessToken = _.get(store.state, ['account', 'accessToken'])
if (accessToken) {
config.headers.common['x-auth-token'] = accessToken
}
return config
}, (error) => Promise.reject(error))
}
Here the store is closed over in the arrow function supplied to axios so when I go to send the request it sees if there is a valid accessToken, and if so then use it.
BUT, and here's the kicker, when a request is made, I look at the store.state.account.accessToken and low and behold its been reinitialized back to the value of "INVALID".
What? Que?
It's almost like the store was reinitialized behind the scenes? Or somehow the state in the plugin is "server side state"?? because if I try and log buildInitialState I don't get any messages indicating that the path that produced a map with INVALID is being run.
Basically, I don't thoroughly understand the initialization pathway Nuxt is taking here at all.
If any Nuxt masters could help me out understand this a bit more that would be great, it's turning into a bit of a show stopper for me.
Essentially! All I want to be able to do is save the user so that when they refresh their page we can keep on running without forcing them to re-login again....I thought that would be simple.
Thanks and regards, Jason.
I've solved this with a bit of experimentation and comments from other posters around what is called SSR and SPA.
Firstly, this https://github.com/nuxt/nuxt.js/issues/1500 thread really helped me and the final comment from #jsonberry steered my mind in the right direction, away from fetch and asyncData.
I finally had a bit more of an understanding of how NUXT.js was separating SSR and SPA calls.
I then tried #robyedlin suggestion of putting localStorage initialization in the created() method for my main layout/default.vue page.
While I made progress with that suggestion it turns out created() is also called SSR and I was still trying to initialize my store from credentials that weren't accessible.
Finally, moving the initialization to mounted() did the trick!
So in summary:
My account store is left alone, I don't try and initialize it when it is created (it's just overwritten at some point when the SSR stuff runs)
On mounted() in layout/defualt.vue I read from localStorage and initialize the account store so I can start making API requests with the appropriate accessToken.
That seems to have done the trick.
I am creating an error hook in my Ember.js app to redirect you to the auth service if you are not allowed to view certain content (in other words, if the server returns a 401).
It looks like this:
Ember.Route = Ember.Route.extend({
error: function(error, transition){
if (error.status === 401) {
window.location.replace("https://auth.censored.co.za");
}
}
Our auth api works as follows: If you send it a parameter called target (which is a url), it will redirect you back to that target url after you've logged in.
So I want to somehow get the URL of the route the Ember app was trying to transition to.
Then my code will end up something like this
Ember.Route = Ember.Route.extend({
error: function(error, transition){
if (error.status === 401) {
var target = // Your answer here
window.location.replace("https://auth.censored.co.za?target=" + encodeURIComponent(target));
}
}
I came across a need for this too, and resorted to using some internal APIs. In my case, I wanted to reload the whole app so that if you're switching users there's not data left over from the other user. When I reload the app, I want to put the user at the URL they tried to transition to, but for which they had insufficient privileges. After they authenticate (and thus have the bearer token in localstorage) I wanted to use window.location.replace(url) to get a clean copy of the whole app with the user at the URL implied by the Ember Transition object. But the question was, how do I go from a Transition object to a URL? Here is my solution, which uses the generate method which is a private API of the router:
let paramsCount = 0;
let params = {};
let args = [transition.targetName];
// Iterate over route segments
for (let key1 in transition.params) {
if (transition.params.hasOwnProperty(key1)) {
let segment = transition.params[key1];
// Iterate over the params for the route segment.
for (let key2 in segment) {
if (segment.hasOwnProperty(key2)) {
if (segment[key2] != null) {
params[key2] = segment[key2];
paramsCount++;
}
}
}
}
}
if (paramsCount > 0) {
args.push(params);
}
let url = router.generate.apply(router, args);
You'll need to get the router somehow either with a container lookup or some other means. I got it by injecting the -routing service which is documented as an API that might be publically exposed in the future (used by link-to), and which happens to have the router as a property.
While messy, perhaps you might find this helpful.
I was able to use transition.intent.url to accomplish exactly this. I'm not sure if this is private or not -- relevant discussion: https://discuss.emberjs.com/t/getting-url-from-ember-router-transition-for-sso-login/7079/2.
After spending several hours searching for an answer to this question and using the Chrome debugger to try and reverse engineer the Ember 2.5 code, my conclusion is that what you are looking for is not possible at the present time.
For people who don't understand why someone wants to do this, the case arises when authentication (e.g the login page) is separated from the application. This is necessary if there is a requirement not to deliver any content (including the application itself) to the user if the user is not authenticated. In other words, the login page cannot be part of the application because the user is not allowed to access the application before logging in.
PS: I realize this is not a solution to the user's question and probably more suitable as a comment. However, I can't post comments.
Kevins answer is the most correct one, I came to a similar solution. Basically I found how the link-to component was populating the href attribute and used the same sort of code.
In your object inject -routing. I did so with:
'routing': Ember.inject.service('-routing'),
Then the code to generate the URL from the transition is as follows...
let routing = this.get('routing');
let params = Object.values(transition.params).filter(param => {
return Object.values(param).length;
});
let url = routing.generateURL(transition.targetName, params, transition.queryParams);
I am writing an application which needs to display some user information.
Because Meteor.user() is not immediately available I wrapped every user information with an handlerbar helper
Handlebars.registerHelper('isLoggingIn', function() {
return Meteor.loggingIn();
})
This worked for me until I needed to create an admin page and custom content for every user/user role.
Waiting for Meteor.user() to be available or showing general information first while waiting for the roles to load are options I would like to avoid.
I then tried an alternative way and published the currentUser with a new Collection.
Meteor.publish('currentUser', function() {
var sub = this;
var handle = Meteor.users.find({_id: this.userId}).observe({
added: function (user) {
sub.added('currentUser', user._id, user);
}
});
sub.ready();
sub.onStop(function() { handle.stop(); });
});
and
CurrentUser = new Meteor.Collection('currentUser');
In this way I can access the logged in user with CurrentUser.findOne(), and it's available at the same time as the other collections.
What I fear is that this alternative is not as secure and problem free as the common Meteor.user(), and I was wondering if my method is correct and if there are better ways to obtain the same result (user detail information immediately available) without reinventing the wheel.
Just a note you can use {{loggingIn}}, {{#if loggingIn}}.. without writing your own helper.
The option to publish the user who is logged in with a custom publish function adds an unnecessary complexity.
When it comes to security you have to assume if its from the client side, in any scenario it is untrustworthy. This means you publish relevant data for the role, etc only when they are logged in to that user.
On the server the data is immediately available as soon as the user logs in, all you have to do is publish only the data for that users role. On the client it may take some time to adjust to this, which is why you can use placeholder until the subscriptions are complete.
What might be a better option would be to use either a helper that checks for when subscriptions are completed and displays a 'loading message'. Or use a router such as iron-router (github.com/EventedMind/iron-router) that can let you wait for a subcription to complete for a particular page.
This way you can use Meteor.user(), {{#currentUser}} and roles in way you intend.
One thing to keep in mind, is if you want to check if the user is logged in, not to use:
if(Meteor.user())
but instead
if(Meteor.user() && Meteor.user().profile && Meteor.user().profile.name)
(You will have to insert a name property in your profile, though). While logging in the user gets more and more data. I've noticed if you wait for the profile field, then the user is 'ready'. It seems initially the profile field is empty (still loggin in), but it would return true if you used if(Meteor.user())
I'm developing a prototype with two simple pages and google plus integration. I have two pages, first one with a "login" button, the second one with a link. When the user clicks on the login button, I am calling:
var params = {"client_id":"<client_id>", "scope":"https://www.googleapis.com/auth/plus.login"};
gapi.auth.authorize(params, signinCallback);
The signinCallback looks like this:
var signinCallback = function(authResult) {
if (authResult['access_token']) {
gapi.auth.setToken(authResult);
gapi.client.load('plus','v1', function(){
var request = gapi.client.plus.people.list({
'userId': 'me',
'collection': 'visible'
});
request.execute(function(resp) {
console.log(resp);
});
});
} else if (authResult['error']) {
console.error('Sign-in state: ' + authResult['error']);
}
}
So when the user clicks the button, signs in and provides permissions to the app, I'm storing the token and making a people list call. This all works perfect.
My problem is when I navigate to the second page and try to make the same call I made before:
gapi.client.load('plus','v1', function(){
var request = gapi.client.plus.people.list({
'userId': 'me',
'collection': 'visible'
});
request.execute(function(resp) {
console.log(resp);
});
});
The call fails with the error: Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.
I thought when I did "setToken" after signing up originally, I wouldn't have to continue authenticating every single subsequent call, what am I doing wrong?
If these are truly two different pages (as opposed to one page that has made some AJAX or other calls to your server to get additional data), then each page has a completely different JavaScript environment. This means that the gapi object is a different copy on each page, and the authentication you've set on the first page hasn't been set on the gapi object on the second page. You are not setting a token for the session - you're setting it on a specific JavaScript object.
If you are using something like the Google+ Sign In, you could put the button on each page, and each page would get its own token when the user visits it, but this is somewhat inefficient, since it also means a round-trip to the server each time.
You could probably also do something like put the authentication token into temporary/session local storage, but you should be careful in this case that the token can not leak out and cause you a security issue.
There are other potential solutions, but it really boils down to how you intend to use the authenticated user as part of your client.
Is there any way to pick up when a user logs out of the website? I need to do some clean up when they do so. Using the built-in meteor.js user accounts.
I'll be doing some validation using it, so I need a solution that cannot be trigger on behalf of other users on the client side - preferably something completely server side.
You may use Deps.autorun to setup a custom handler observing Meteor.userId() reactive variable changes.
Meteor.userId() (and Meteor.user()) are reactive variables returning respectively the currently logged in userId (null if none) and the corresponding user document (record) in the Meteor.users collection.
As a consequence one can track signing in/out of a Meteor application by reacting to the modification of those reactive data sources.
client/main.js :
var lastUser=null;
Meteor.startup(function(){
Deps.autorun(function(){
var userId=Meteor.userId();
if(userId){
console.log(userId+" connected");
// do something with Meteor.user()
}
else if(lastUser){
console.log(lastUser._id+" disconnected");
// can't use Meteor.user() anymore
// do something with lastUser (read-only !)
Meteor.call("userDisconnected",lastUser._id);
}
lastUser=Meteor.user();
});
});
In this code sample, I'm setting up a source file local variable (lastUser) to keep track of the last user that was logged in the application.
Then in Meteor.startup, I use Deps.autorun to setup a reactive context (code that will get re-executed whenever one of the reactive data sources accessed is modified).
This reactive context tracks Meteor.userId() variation and reacts accordingly.
In the deconnection code, you can't use Meteor.user() but if you want to access the last user document you can use the lastUser variable.
You can call a server method with the lastUser._id as argument if you want to modify the document after logging out.
server/server.js
Meteor.methods({
userDisconnected:function(userId){
check(userId,String);
var user=Meteor.users.findOne(userId);
// do something with user (read-write)
}
});
Be aware though that malicious clients can call this server method with anyone userId, so you shouldn't do anything critical unless you setup some verification code.
Use the user-status package that I've created: https://github.com/mizzao/meteor-user-status. This is completely server-side.
See the docs for usage, but you can attach an event handler to a session logout:
UserStatus.events.on "connectionLogout", (fields) ->
console.log(fields.userId + " with connection " + fields.connectionId + " logged out")
Note that a user can be logged in from different places at once with multiple sessions. This smart package detects all of them as well as whether the user is online at all. For more information or to implement your own method, check out the code.
Currently the package doesn't distinguish between browser window closes and logouts, and treats them as the same.
We had a similar, though not exact requirement. We wanted to do a bit of clean up on the client when they signed out. We did it by hijacking Meteor.logout:
if (Meteor.isClient) {
var _logout = Meteor.logout;
Meteor.logout = function customLogout() {
// Do your thing here
_logout.apply(Meteor, arguments);
}
}
The answer provided by #saimeunt looks about right, but it is a bit fluffy for what I needed. Instead I went with a very simple approach like this:
if (Meteor.isClient) {
Deps.autorun(function () {
if(!Meteor.userId())
{
Session.set('store', null);
}
});
}
This is however triggered during a page load if the user has not yet logged in, which might be undesirable. So you could go with something like this instead:
if (Meteor.isClient) {
var userWasLoggedIn = false;
Deps.autorun(function (c) {
if(!Meteor.userId())
{
if(userWasLoggedIn)
{
console.log('Clean up');
Session.set('store', null);
}
}
else
{
userWasLoggedIn = true;
}
});
}
None of the solutions worked for me, since they all suffered from the problem of not being able to distinguish between manual logout by the user vs. browser page reload/close.
I'm now going with a hack, but at least it works (as long as you don't provide any other means of logging out than the default accounts-ui buttons):
Template._loginButtons.events({
'click #login-buttons-logout': function(ev) {
console.log("manual log out");
// do stuff
}
});
You can use the following Meteor.logout - http://docs.meteor.com/#meteor_logout