Meteor Tutorial - Let's build a Twitter clone (Part 3: User Account Management)

This is part 3 of the twitterClone Meteor tutorial.

Part 1 - Intro to Meteor link
Part 2 - Client Template JS link
Part 3 - User Account link
Part 4 - Security & Structure link
Part 5 - Server Methods link
Part 6 - Data Publish/Subscribe link

We will be building a user authentication system in this tutorial. As always, you can check out the code at github

Here is a preview of what we are building:

Section 1: Meteor User Accounts Package

Meteor makes the process of user management very easy. Let's go ahead and add the meteor packages:

meteor add accounts-ui accounts-password

The meteor packages comes with HTML template, you can go ahead and drop this in html:

{{> loginButtons}}

You will see a super basic signup/login UI:

Meteor gives you user account management, complete with bcrypt, user db operations, session management, auth logic right out of the box. I have never been able to do all of this in one line of HTML before.

If you try to create a user now, you can see in the mongodb console:

meteor:PRIMARY> db.users.find()
{ "_id" : "iw75fJLwPNXwDrPWN", "createdAt" : ISODate("2015-07-11T20:49:30.702Z"), "services" : { "password" : { "bcrypt" : "$2a$10$2Od1oJZm693aRzIyYNIVVO4XyAuU0pXrcsrHgvuu0p0MWbp1aSzGW" }, "resume" : { "loginTokens" : [ ] } }, "emails" : [ { "address" : "test@test.com", "verified" : false } ] }

Section 2: Customize Login UI (Signup)

I want to customize the user experience so it conforms to a twitter-like experience. I am going to create my own user management template:

twitterClone.css

CSS on github

twitterClone.html

<template name="userManagement">

  <h4>New User?</h4>
  <div class="form-group">
    <input class="form-control input-sm" id="signup-username" placeholder="Username">
    <input class="form-control input-sm" id="signup-fullname" placeholder="Full Name (Optional)">
    <input class="form-control input-sm" id="signup-password" placeholder="Password" type="password">
  </div>

  <button type="button" class="btn btn-info fullbutton" id="signup">Sign up</button>

</template>

twitterClone.js

Template.userManagement.events({
  'click #signup': function() {
    var user = {
      username: $('#signup-username').val(),
      password: $('#signup-password').val(),
      profile: {
        fullname: $('#signup-fullname').val()
      }
    };

    Accounts.createUser(user, function (error) {
      if(error) alert(error);
    });
  }
});

We are creating a new template userManagement. The javascript code is listening to clicks on signup button. A new user variable is created and processed through Accounts.createUser.

Accounts.createUser() method is made available through the accounts-ui package. It comes with a variety of customization and methods (doc). You can also add third-party authentication through services like twitter, facebook, google, etc. by adding their accounts packages.

Section 3: Customize Login UI (Login)

Login module can be created in a similiar manner:

twitterClone.html

<template name="userManagement">
  ...

  <h4>Already have an account?</h4>
  <div class="form-group">
    <input class="form-control input-sm" id="login-username" placeholder="Username">
    <input class="form-control input-sm" id="login-password" placeholder="Password" type="password">
  </div>

  <button type="button" class="btn btn-info fullbutton login" id="login">Log in</button>

</template>

twitterClone.js

Template.userManagement.events({
  'click #login': function() {
    var username = $('#login-username').val();
    var password = $('#login-password').val();

    Meteor.loginWithPassword(username, password, function(error) {
      if(error) alert(error);
    });
  }
});

Meteor.loginWithPassword() will try to log the user in via the username and password provided. You don't have to worry about the process of encryption & validation here at all. If the validation is correct, browser cookie will be set for you. If validation fails, the callback function will have error.

At this point, you can bring out your browser console and type in Meteor.user() to see the logged in user. The same method will return null if you are not logged in.

Section 4: Customize Login UI (Putting all together)

We want the user to know whether she is currently logged in. And we want to make sure you can logout if you so choose. Let's put all the pieces we have built together to complete the user management picture.

twitterClone.html

<body>
  <div class="row">
    <div class="col-md-4 col-sm-4">{{> userManagement}}</div>
    <div class="col-md-8 col-sm-8">{{> tweetBox}}</div>
  </div>
</body>

<template name="userManagement">
  <div class="user-container">
    <div class="panel panel-default userBox">
      <div class="panel-body">
        {{# if currentUser}}
        <!-- Message for logged in user -->
        <p>Hello <strong>@{{currentUser.username}}</strong>, welcome to twitterClone</p>

        {{else}}
        <!-- Log in module -->
        <h4>Already have an account?</h4>
        <div class="form-group">
          <input class="form-control input-sm" id="login-username" placeholder="Username">
          <input class="form-control input-sm" id="login-password" placeholder="Password" type="password">
        </div>

        <button type="button" class="btn btn-info fullbutton login" id="login">Log in</button>


        <!-- Sign up module -->
        <h4>New User?</h4>
        <div class="form-group">
          <input class="form-control input-sm" id="signup-username" placeholder="Username">
          <input class="form-control input-sm" id="signup-fullname" placeholder="Full Name (Optional)">
          <input class="form-control input-sm" id="signup-password" placeholder="Password" type="password">
        </div>

        <button type="button" class="btn btn-info fullbutton" id="signup">Sign up</button>
        {{/if}}

      </div>
    </div>
  </div>
</template>

We have put in some Blaze template code to show logged in user (Blaze template is based on Handlebars for those familiar).
{{# if currentUser}}
{{else}}
{{/if}}
block will show a welcome message if user is logged in, or the login/signup form if the user is not logged in. currentUser is calling a helper method that is part of Meteor user-accounts package (doc).

<p>Hello <strong>@{{currentUser.username}}</strong>, welcome to twitterClone</p>

We are able to directly access the user's username through {{currentUser.username}} because currentUser returns the entire user object to the browser.

Our user management module is now complete!

Section 4: Logging out

Logging a user out is as easy as logging a user in.

twitterClone.html

<button type="button" class="btn btn-info fullbutton" id="logout">Log out</button>

twitterClone.js

'click #logout': function() {
  Meteor.logout();
}

Section 5: Assign Tweets to Users

Now that we have user information. We can assign the currentUser username to each tweet.

twitterClone.js

Template.tweetBox.events({
  'click button': function() {
    ...
    Tweets.insert({message: tweet, user: Meteor.user().username});
  }
});

Meteor.user() is an accessibility method for developers to get current logged in user's information. You might be concerned that non-logged in users can post stuff! Well, let's wrap the insert in a user check:

if (Meteor.user()) {
  Tweets.insert({message: tweet, user: Meteor.user().username});
}

Additionally, let's prevent users from even clicking tweet if they aren't logged in:

Template.tweetBox.helpers({

  disableButton: function() {
    if (Session.get('numChars') <= 0 ||
        Session.get('numChars') > 140 ||
        !Meteor.user()) {
      return 'disabled';
    }
  }

});

We will change the button message to make sure users understand why the post button is disabled in the UI:

twitterClone.html

{{#if currentUser}}
  <button class="btn btn-info pull-right" type="button" {{disableButton}}>Tweet</button>
{{else}}
  <button class="btn btn-info pull-right" type="button" disabled>Please Log In</button>
{{/if}}

Let's see the mongodb console again:

meteor:PRIMARY> db.tweets.find()
{ "_id" : "myxrQmPWbKDrKDcr9", "message" : "hello world", "user" : "l33thax" }

Great! we have the poster's information in the database.

Part 1 - Intro to Meteor link
Part 2 - Client Template JS link
Part 3 - User Account link
Part 4 - Security & Structure link
Part 5 - Server Methods link
Part 6 - Data Publish/Subscribe link