Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Meteor: Full-Stack Web Application Development

You're reading from   Meteor: Full-Stack Web Application Development Rapidly build web apps with Meteor

Arrow left icon
Product type Course
Published in Nov 2016
Publisher Packt
ISBN-13 9781787287754
Length 685 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Authors (2):
Arrow left icon
Fabian Vogelsteller Fabian Vogelsteller
Author Profile Icon Fabian Vogelsteller
Fabian Vogelsteller
Marcelo Reyna Marcelo Reyna
Author Profile Icon Marcelo Reyna
Marcelo Reyna
Arrow right icon
View More author details
Toc

Chapter 7. Users and Permissions

Having worked through the previous chapters, we should have a working blog by now. We can click on all links and posts, and even lazy load more posts.

In this chapter, we will add our backend login and create the admin user. We will also create the template to edit posts and make an edit button visible to the admin user so that they can edit and add new content.

In this chapter, we will learn the following concepts:

  • Meteor's accounts package
  • Creating users and a log in
  • How to restrict certain routes to only logged-in users

    Note

    You can delete all the session examples from the previous chapter, as we won't need them to progress with our app. Delete the session's code from my-meteor-blog/main.js, my-meteor-blog/client/templates/home.js, and my-meteor-blog/client/templates/home.html, or download a fresh copy of the previous chapter's code.

    If you've jumped right into the chapter and want to follow the examples, download the previous chapter's code examples from either the book's web page at https://www.packtpub.com/books/content/support/17713 or from the GitHub repository at https://github.com/frozeman/book-building-single-page-web-apps-with-meteor/tree/chapter6.

    These code examples will also contain all the style files, so we don't have to worry about adding CSS code along the way.

Meteor's accounts packages

Meteor makes it very easy to add authentication to our web app using its accounts package. The accounts package is a complete login solution tied to Meteor's core. Created users can be identified by ID in many of Meteor's server-side functions, for example, in a publication:

Meteor.publish("examplePublication", function () {
  // the current loggedin user id can be accessed via
  this.userId;
}

Additionally, we can add support for login via Facebook, GitHub, Google, Twitter, Meetup, and Weibo by simply adding one or more of the accounts-* core packages.

Meteor also comes with a simple login interface, an extra template that can be added using the {{> loginButtons}} helper.

All registered user profiles will be stored in a collection called Users, which Meteor creates for us. All the processes in authentication and communication use the Secure Remote Password (SRP) protocol and most external services use OAuth.

For our blog, we will simply create one admin user, which when logged in will be able to create and edit posts.

Note

If we want to use one of the third-party services to log in, we can work through this chapter first, and then add one of the previously mentioned packages.

After we add the additional packages, we can open up the Sign in form. We will see a button where we can configure the third-party services for use with our app.

Adding the accounts packages

To start using a login system, we need to add the accounts-ui and accounts-password packages to our app, as follows:

  1. To do so, we open up the terminal, navigate to our my-meteor-blog folder, and type the following command:
    $ meteor add accounts-ui accounts-password
    
  2. After we have successfully added the packages, we can run our app again using the meteor command.
  3. As we want to prevent the creation of additional user accounts by our visitors, we need to disallow this functionality in our accounts package, config. First, we need to open up our my-meteor-blog/main.js file, which we created in the previous chapter, and remove all of the code, as we won't need the session examples anymore.
  4. Then add the following lines of code to this file, but make sure you don't use if(Meteor.isClient), as we want to execute the code on both the client and the server this time:
    Accounts.config({
        forbidClientAccountCreation: true
    });

    This will forbid any call of Accounts.createUser() on the client and the accounts-ui package will not show the Register button to our visitors.

    Note

    This option doesn't seem to work for third-party services. So, when using third-party services, everybody can sign up and edit posts. To prevent this, we will need to create "deny" rules for user creation on the server side, which is beyond the scope of this chapter.

Adding admin functionality to our templates

The best way to allow editing of our post is to add an Edit post link to our post's page, which can only be seen if we are logged in. This way, we save rebuilding a similar infrastructure for an additional backend, and make it easy to use as there is no strong separation between frontend and backend.

First, we will add a Create new post link to our home template, then add the Edit post link to the post's pages template, and finally add the login buttons and form to the main menu.

Adding a link for new posts

Let's begin by adding a Create new post link. Open the home template at my-meteor-blog/clients/templates/home.html and add the following lines of code just above the {{#each postsList}} block helper:

{{#if currentUser}}
    <a href="/create-post" class="createNewPost">Create new post</a>
{{/if}}

The {{currentUser}} helper comes with the accounts-base package, which was installed when we installed our accounts packages. It will return the current logged-in user, or return null if no user is logged in. Using it inside an {{#if}} block helper allows us to show content only to logged-in users.

Adding the link to edit posts

To edit posts, we simply add an Edit post link to our post template. Open up post.html from the same folder and add {{#if currentUser}}..{{/if}} after {{author}}, as follows:

<small>
    Posted {{formatTime timeCreated "fromNow"}} by {{author}}

    {{#if currentUser}}
        | <a href="/edit-post/{{slug}}">Edit post</a>
    {{/if}}
</small>

Adding the login form

Now that we have both links to add and edit posts, let's add the login form. We can create our own form, but Meteor already comes with a simple login form, which we can style to fit our design.

Since we added the accounts-ui package previously, Meteor provides us with the {{> loginButtons}} template helper, which works as a drop-in-place template. To add this, we will open our layout.html template and add the following helper inside our menu's <ul></ul> tags, as follows:

<h1>My Meteor Single Page App</h1>
<ul>
    <li>
        <a href="/">Home</a>
    </li>
    <li>
        <a href="/about">About</a>
    </li>

</ul>

{{> loginButtons}}

Adding a link for new posts

Let's begin by adding a Create new post link. Open the home template at my-meteor-blog/clients/templates/home.html and add the following lines of code just above the {{#each postsList}} block helper:

{{#if currentUser}}
    <a href="/create-post" class="createNewPost">Create new post</a>
{{/if}}

The {{currentUser}} helper comes with the accounts-base package, which was installed when we installed our accounts packages. It will return the current logged-in user, or return null if no user is logged in. Using it inside an {{#if}} block helper allows us to show content only to logged-in users.

Adding the link to edit posts

To edit posts, we simply add an Edit post link to our post template. Open up post.html from the same folder and add {{#if currentUser}}..{{/if}} after {{author}}, as follows:

<small>
    Posted {{formatTime timeCreated "fromNow"}} by {{author}}

    {{#if currentUser}}
        | <a href="/edit-post/{{slug}}">Edit post</a>
    {{/if}}
</small>

Adding the login form

Now that we have both links to add and edit posts, let's add the login form. We can create our own form, but Meteor already comes with a simple login form, which we can style to fit our design.

Since we added the accounts-ui package previously, Meteor provides us with the {{> loginButtons}} template helper, which works as a drop-in-place template. To add this, we will open our layout.html template and add the following helper inside our menu's <ul></ul> tags, as follows:

<h1>My Meteor Single Page App</h1>
<ul>
    <li>
        <a href="/">Home</a>
    </li>
    <li>
        <a href="/about">About</a>
    </li>

</ul>

{{> loginButtons}}

Adding the link to edit posts

To edit posts, we simply add an Edit post link to our post template. Open up post.html from the same folder and add {{#if currentUser}}..{{/if}} after {{author}}, as follows:

<small>
    Posted {{formatTime timeCreated "fromNow"}} by {{author}}

    {{#if currentUser}}
        | <a href="/edit-post/{{slug}}">Edit post</a>
    {{/if}}
</small>

Adding the login form

Now that we have both links to add and edit posts, let's add the login form. We can create our own form, but Meteor already comes with a simple login form, which we can style to fit our design.

Since we added the accounts-ui package previously, Meteor provides us with the {{> loginButtons}} template helper, which works as a drop-in-place template. To add this, we will open our layout.html template and add the following helper inside our menu's <ul></ul> tags, as follows:

<h1>My Meteor Single Page App</h1>
<ul>
    <li>
        <a href="/">Home</a>
    </li>
    <li>
        <a href="/about">About</a>
    </li>

</ul>

{{> loginButtons}}

Adding the login form

Now that we have both links to add and edit posts, let's add the login form. We can create our own form, but Meteor already comes with a simple login form, which we can style to fit our design.

Since we added the accounts-ui package previously, Meteor provides us with the {{> loginButtons}} template helper, which works as a drop-in-place template. To add this, we will open our layout.html template and add the following helper inside our menu's <ul></ul> tags, as follows:

<h1>My Meteor Single Page App</h1>
<ul>
    <li>
        <a href="/">Home</a>
    </li>
    <li>
        <a href="/about">About</a>
    </li>

</ul>

{{> loginButtons}}

Creating the template to edit posts

Now we are only missing the template to edit the posts. To add this, we will create a file called editPost.html inside our my-meteor-blog/client/templates folder, and fill it with the following lines of code:

<template name="editPost">
  <div class="editPost">
     <form>
        <label>
          Title
          <input type="text" name="title" placeholder="Awesome title" value="{{title}}">
        </label>
        
        <label>
          Description
          <textarea name="description" placeholder="Short description displayed in posts list" rows="3">{{description}}</textarea>
        </label>

        <label>
          Content
          <textarea name="text" rows="10" placeholder="Brilliant content">{{text}}</textarea>
        </label>

        <button type="submit" class="save">Save Post</button>
    </form>
  </div>
</template>

As we can see, we have added the helpers for {{title}}, {{description}}, and {{text}}, which will come later from the post's data. This simple template, with its three text fields, will allow us to edit and create new posts later.

If we now check out our browser, we will notice that we can't see any of the changes we made so far, apart from the Sign in link in the corner of our website. To be able to log in, we first need to add our admin user.

Creating the admin user

Since we deactivated the creation of users from the client, as a security measure we will create the admin user on the server in the same way we created our example posts.

Open the my-meteor-blog/server/main.js file and add the following lines of code somewhere inside Meteor.startup(function(){...}):

if(Meteor.users.find().count() === 0) {

    console.log('Created Admin user');

    Accounts.createUser({
        username: 'johndoe',
        email: 'johndoe@example.com',
        password: '1234',
        profile: {
            name: 'John Doe'
        }
    });
}

If we now go to our browser, we should be able to log in using the user we just created, and we immediately see that all the edit links appear.

However, when we click any of the edit links, we will see the notFound template appearing because we didn't create any of our admin routes yet.

Adding permissions

Meteor's account package doesn't come by default with configurable permissions for users.

To add permission control, we can add a third-party package such as the deepwell:authorization package, which can be found on Atmosphere at http://atmospherejs.com/deepwell/authorization and which comes with a complex role model.

If we want to do it manually, we can add the simple roles properties to our user document when we create the user, and then check for these roles in our allow/deny roles when we create or update posts. We will learn about allow/deny rules in the next chapter.

If we create a user using the Accounts.createUser() function, we can't add a custom property, so we need to update the user document after we have created the user, as follows:

var userId = Accounts.createUser({
  username: 'johndoe',
  email: 'johndoe@example.com',
  password: '1234',
  profile: {
    name: 'John Doe'
  }
});
// add the roles to our user
Meteor.users.update(userId, {$set: {
  roles: {admin: true},
}})

By default, Meteor publishes the username, emails, and profile properties of the currently logged-in user. To add additional properties, such as our custom roles property, we need to add a publication, to access the roles property on the client as well, as follows:

  1. Open the my-meteor/blog/server/publications.js file and add the following publication:
    Meteor.publish("userRoles", function () {
     if (this.userId) {
      return Meteor.users.find({_id: this.userId}, {fields: {roles: 1}});
     } else {
      this.ready();
     }
    });
  2. In the my-meteor-blog/main.js file, we add the subscription as follows:
    if(Meteor.isClient){
      Meteor.subscribe("userRoles");
    }
  3. Now that we have the roles property available on the client, we can change {{#if currentUser}}..{{/if}} in the home and post templates to {{#if currentUser.roles.admin}}..{{/if}} so that only admins can see the buttons.

A note on security

The user can only update their own profile property using the following command:

Meteor.users.update(ownUserId, {$set: {profiles:{myProperty: 'xyz'}}})

If we want to update the roles property, we will fail. To see this in action, we can open up the browser's console and type the following command:

Meteor.users.update(Meteor.user()._id, {$set:{ roles: {admin: false}}});

This will give us an error stating: update failed: Access denied, as shown in the following screenshot:

A note on security

Note

If we want to allow users to edit other properties such as their roles property, we need to add a Meteor.users.allow() rule for that.

Adding permissions

Meteor's account package doesn't come by default with configurable permissions for users.

To add permission control, we can add a third-party package such as the deepwell:authorization package, which can be found on Atmosphere at http://atmospherejs.com/deepwell/authorization and which comes with a complex role model.

If we want to do it manually, we can add the simple roles properties to our user document when we create the user, and then check for these roles in our allow/deny roles when we create or update posts. We will learn about allow/deny rules in the next chapter.

If we create a user using the Accounts.createUser() function, we can't add a custom property, so we need to update the user document after we have created the user, as follows:

var userId = Accounts.createUser({
  username: 'johndoe',
  email: 'johndoe@example.com',
  password: '1234',
  profile: {
    name: 'John Doe'
  }
});
// add the roles to our user
Meteor.users.update(userId, {$set: {
  roles: {admin: true},
}})

By default, Meteor publishes the username, emails, and profile properties of the currently logged-in user. To add additional properties, such as our custom roles property, we need to add a publication, to access the roles property on the client as well, as follows:

  1. Open the my-meteor/blog/server/publications.js file and add the following publication:
    Meteor.publish("userRoles", function () {
     if (this.userId) {
      return Meteor.users.find({_id: this.userId}, {fields: {roles: 1}});
     } else {
      this.ready();
     }
    });
  2. In the my-meteor-blog/main.js file, we add the subscription as follows:
    if(Meteor.isClient){
      Meteor.subscribe("userRoles");
    }
  3. Now that we have the roles property available on the client, we can change {{#if currentUser}}..{{/if}} in the home and post templates to {{#if currentUser.roles.admin}}..{{/if}} so that only admins can see the buttons.

A note on security

The user can only update their own profile property using the following command:

Meteor.users.update(ownUserId, {$set: {profiles:{myProperty: 'xyz'}}})

If we want to update the roles property, we will fail. To see this in action, we can open up the browser's console and type the following command:

Meteor.users.update(Meteor.user()._id, {$set:{ roles: {admin: false}}});

This will give us an error stating: update failed: Access denied, as shown in the following screenshot:

A note on security

Note

If we want to allow users to edit other properties such as their roles property, we need to add a Meteor.users.allow() rule for that.

A note on security

The user can only update their own profile property using the following command:

Meteor.users.update(ownUserId, {$set: {profiles:{myProperty: 'xyz'}}})

If we want to update the roles property, we will fail. To see this in action, we can open up the browser's console and type the following command:

Meteor.users.update(Meteor.user()._id, {$set:{ roles: {admin: false}}});

This will give us an error stating: update failed: Access denied, as shown in the following screenshot:

A note on security

Note

If we want to allow users to edit other properties such as their roles property, we need to add a Meteor.users.allow() rule for that.

Creating routes for the admin

Now that we have an admin user, we can add the routes, which lead to the editPost template. Though in theory the editPost template is available to every client, it doesn't create any risk, as the allow and deny rules are the real security layer, which we will take a look at in the next chapter.

To add the route to create posts, let's open up our my-meteor-blog/routes.js file and add the following route to the Router.map() function:

this.route('Create Post', {
    path: '/create-post',
    template: 'editPost'
});

This will simply show the editPost template as soon as we click on the Create new post link on our home page, as shown in the following screenshot:

Creating routes for the admin

We see that the form is empty because we did not set any data context to the template, and therefore the {{title}}, {{description}}, and {{text}} placeholders in the template displayed nothing.

To make the edit post route work, we need to add subscriptions similar to those we did for the Post route itself. To keep things DRY (which means Don't Repeat Yourself), we can create a custom controller, which both routes will use, as follows:

  1. Add the following lines of code after the Router.configure(...); call:
    PostController = RouteController.extend({
        waitOn: function() {
            return Meteor.subscribe('single-post', this.params.slug);
        },
    
        data: function() {
            return Posts.findOne({slug: this.params.slug});
        }
    });
  2. Now we can simply edit the Post route, remove the waitOn() and data() functions, and add PostController instead:
    this.route('Post', {
        path: '/posts/:slug',
        template: 'post',
        controller: 'PostController'
    });
  3. Now we can also add the Edit Post route by just changing the path and the template properties:
    this.route('Edit Post', {
        path: '/edit-post/:slug',
        template: 'editPost',
        controller: 'PostController'
    });
  4. That's it! When we now go to our browser, we will be able to access any post and click on the Edit button, and we will be directed to editPost template.

If you are wondering why the form is filled in with the post data, take a look at PostController, which we just created. Here, we return the post document inside the data() function, setting the data context of the template to the post's data.

Now that we have these routes in place, we should be done. Shouldn't we?

Not yet, because everybody who knows the /create-post and /edit-post/my-title routes can simply see the editPost template, even if he or she is not an admin.

Preventing visitors from seeing the admin routes

To prevent visitors from seeing admin routes, we need to check whether the user is logged in before we show them the routes. The iron:router comes with a Router.onBeforeAction() hook, which can be run for all or some routes. We will use this to run a function to check whether the user is logged in; if not, we will pretend that the route doesn't exist and simply display the notFound template.

Add the following code snippet at the end of the routes.js file:

var requiresLogin = function(){
    if (!Meteor.user() ||
        !Meteor.user().roles ||
        !Meteor.user().roles.admin) {
        this.render('notFound');

    } else {
        this.next();
    }
}; 

Router.onBeforeAction(requiresLogin, {only: ['Create Post','Edit Post']});

Here, first we create the requiresLogin() function, which will be executed before the Create Post and Edit Post routes because we pass them as the second arguments to the Router.onBeforeAction() function.

Inside the requiresLogin(), we check whether the user is logged in, which will return the user document when calling Meteor.user(), and if they have the role admin. If not, we simply render the notFound template and don't continue to the route. Otherwise, we run this.next(), which will continue to render the current route.

That's it! If we now log out and navigate to the /create-post route, we will see the notfound template.

If we log in, the template will switch and immediately show the editPost template.

This happens because the requiresLogin() function becomes reactive as soon as we pass it to Router.onBeforeAction(), and since Meteor.user() is a reactive object, any change to the user's status will rerun this function.

Now that we have created the admin user and the necessary templates, we can move on to actually creating and editing the posts.

Preventing visitors from seeing the admin routes

To prevent visitors from seeing admin routes, we need to check whether the user is logged in before we show them the routes. The iron:router comes with a Router.onBeforeAction() hook, which can be run for all or some routes. We will use this to run a function to check whether the user is logged in; if not, we will pretend that the route doesn't exist and simply display the notFound template.

Add the following code snippet at the end of the routes.js file:

var requiresLogin = function(){
    if (!Meteor.user() ||
        !Meteor.user().roles ||
        !Meteor.user().roles.admin) {
        this.render('notFound');

    } else {
        this.next();
    }
}; 

Router.onBeforeAction(requiresLogin, {only: ['Create Post','Edit Post']});

Here, first we create the requiresLogin() function, which will be executed before the Create Post and Edit Post routes because we pass them as the second arguments to the Router.onBeforeAction() function.

Inside the requiresLogin(), we check whether the user is logged in, which will return the user document when calling Meteor.user(), and if they have the role admin. If not, we simply render the notFound template and don't continue to the route. Otherwise, we run this.next(), which will continue to render the current route.

That's it! If we now log out and navigate to the /create-post route, we will see the notfound template.

If we log in, the template will switch and immediately show the editPost template.

This happens because the requiresLogin() function becomes reactive as soon as we pass it to Router.onBeforeAction(), and since Meteor.user() is a reactive object, any change to the user's status will rerun this function.

Now that we have created the admin user and the necessary templates, we can move on to actually creating and editing the posts.

Summary

In this chapter, we learned how to create and log in users, how we can show content and templates only to logged-in users, and how routes can be altered depending on the login status.

To learn more, take a look at the following links:

You can find this chapter's code examples at https://www.packtpub.com/books/content/support/17713 or on GitHub at https://github.com/frozeman/book-building-single-page-web-apps-with-meteor/tree/chapter7.

In the next chapter, we will learn how we can create and update posts and how to control updates to the database from the client side.

You have been reading a chapter from
Meteor: Full-Stack Web Application Development
Published in: Nov 2016
Publisher: Packt
ISBN-13: 9781787287754
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image