There are plenty of tutorials around the web that show you how to set up user authentication in Meteor. Most of these tutorials are using accounts-ui to quickly set up authentication in your views. While this can be very useful for including authentication in a prototype app, I would recommend building these views yourself before deploying an app to production.

Also, the majority of Meteor tutorials out there are using Flow Router for handling routes between views. Support for Flow Router in React is fantastic, but learning React Router will be much more useful when using React with something other than Meteor on the back end.

In this tutorial we will be writing a very simple Meteor app with custom built login and signup pages. There will be one page that is password protected and will require the user to be signed in. Our front end code will be written in React and we’ll use React Router for routing between views.

First, make sure that you have Meteor installed on your machine. You’ll also need Node JS and NPM for running the app and downloading the necessary packages we’ll be using. After you have everything installed, let’s start our project.

$ meteor create auth-app
$ cd auth-app
$ meteor npm install --save react react-dom react-router 
$ meteor npm install --save react-addons-pure-render-mixin react-router-dom
$ meteor add accounts-password react-meteor-data twbs:bootstrap

After just a few commands, we’ve created a Meteor project and installed the necessary packages we’ll need to build our app.

Replace the contents of client/main.html with this:

client/main.html

<head>
  <title>Auth App</title>
</head>

<body>
  <div id="target"></div>
</body>

The “target” div is where we will be rendering our app’s React components. Let’s try rendering a component right now.

Rename client/main.js to client/main.jsx and replace all of the file’s code with this:

client/main.jsx

import React from 'react'
import { Meteor } from 'meteor/meteor'
import { render } from 'react-dom'

Meteor.startup(() => {
  render(<h1>Test</h1>, document.getElementById('target'));
});

Here we’re passing an anonymous function to Meteor’s startup() function which will execute as soon as the DOM is ready. This function will call React’s render() function and render the component passed as the first parameter inside our div. In this case, the component being passed is just a heading that says “Test”, but we’ll be replacing this with something else very soon.

At this point we should try opening our app in a browser to make sure our code is working correctly. Try running meteor inside the app’s directory in a terminal and opening localhost:3000 in your browser. You should see the word “Test” if everything runs smoothly.

Now that we have components rendering properly, it’s time to set up routing in our app. Let’s add some components and a route file to the project:

new files

imports/startup/client/routes.jsx               # react router configuration
imports/ui/containers/AppContainer.jsx          # password protected container
imports/ui/containers/MainContainer.jsx         # a container to
imports/ui/pages/LoginPage.jsx                  # login page
imports/ui/pages/SignupPage.jsx                 # signup page
imports/ui/pages/MainPage.jsx                   # password protected page

We’re including our components inside an “imports” directory in our project. Meteor has a few conventions you should be aware of when building out your app’s directory structure. Scripts within certain directories in a Meteor application will be executed on the client, the server, or both depending on which directory they are included in.

imports/startup/client/routes.jsx

import React from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom';

// containers
import AppContainer from '../../ui/containers/AppContainer.jsx'
import MainContainer from '../../ui/containers/MainContainer.jsx'

// pages
import SignupPage from '../../ui/pages/SignupPage.jsx'
import LoginPage from '../../ui/pages/LoginPage.jsx'

export const renderRoutes = () => (
  <Router>
    <div>
      <Route path="/login" component={LoginPage}/>
      <Route path="/signup" component={SignupPage}/>
      <Route exact={true} path="/" component={AppContainer}/>
    </div>
  </Router>
);


The routes file in our app has three routes: one for login, one for signup, and an index route. The index route is nested within an AppContainer, which we will use to make sure users are logged in before they can see the main page of our app.

We’re also nesting our MainPage inside a MainContainer component to send data to our main page. Meteor provides a “createContainer” method to help us access reactive data sources within our rendered views.

imports/ui/containers/AppContainer.jsx

import React, { Component } from 'react';
import { withHistory } from 'react-router-dom';
import MainContainer from './MainContainer.jsx';

export default class AppContainer extends Component {
  constructor(props){
    super(props);
    this.state = this.getMeteorData();
    this.logout = this.logout.bind(this);
  }

  getMeteorData(){
    return { isAuthenticated: Meteor.userId() !== null };
  }

  componentWillMount(){
    if (!this.state.isAuthenticated) {
      this.props.history.push('/login');
    }
  }

  componentDidUpdate(prevProps, prevState){
    if (!this.state.isAuthenticated) {
      this.props.history.push('/login');
    }
  }

  logout(e){
    e.preventDefault();
    Meteor.logout( (err) => {
        if (err) {
            console.log( err.reason );
        } else {
            this.props.history.push('/login');
        }
    });
  }

  render(){
    return (
      <div>
        <nav className="navbar navbar-default navbar-static-top">
          <div className="container">
            <div className="navbar-header">
              <a className="navbar-brand" href="#">Auth App</a>
            </div>
            <div className="navbar-collapse">
              <ul className="nav navbar-nav navbar-right">
                <li>
                  <a href="#" onClick={this.logout}>Logout</a>
                </li>
              </ul>
            </div>
          </div>
        </nav>
        <MainContainer />
      </div>
    );
  }
}


This container will check to see if there is a user logged into a session before rendering. If no user session has been found, the user will be redirected to the login page.

We’ve also included a navigation bar at the top of the container. In a real application I would recommend abstracting this nav bar into its own separate component. I’ve decided to include this navigation bar directly in the render method to reduce the number of files we need for this tutorial.

imports/ui/pages/LoginPage.jsx

import React, { Component } from 'react'
import { withHistory, Link } from 'react-router-dom'
import { createContainer } from 'meteor/react-meteor-data'

export default class LoginPage extends Component {
  constructor(props){
    super(props);
    this.state = {
      error: ''
    };
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(e){
    e.preventDefault();
    let email = document.getElementById('login-email').value;
    let password = document.getElementById('login-password').value;
    Meteor.loginWithPassword(email, password, (err) => {
      if(err){
        this.setState({
          error: err.reason
        });
      } else {
        this.props.history.push('/');
      }
    });
  }

  render(){
    const error = this.state.error;
    return (
      <div className="modal show">
        <div className="modal-dialog">
          <div className="modal-content">
            <div className="modal-header">
              <h1 className="text-center">Login</h1>
            </div>
            <div className="modal-body">
              { error.length > 0 ?
                <div className="alert alert-danger fade in">{error}</div>
                :''}
              <form  id="login-form"
                    className="form col-md-12 center-block"
                    onSubmit={this.handleSubmit}>
                <div className="form-group">
                  <input type="email"
                        id="login-email"
                        className="form-control input-lg"
                        placeholder="email"/>
                </div>
                <div className="form-group">
                  <input type="password"
                        id="login-password"
                        className="form-control input-lg"
                        placeholder="password"/>
                </div>
                <div className="form-group text-center">
                  <input type="submit"
                        id="login-button"
                        className="btn btn-primary btn-lg btn-block"
                        value="Login" />
                </div>
                <div className="form-group text-center">
                  <p className="text-center">
                    Don't have an account? Register <Link to="/signup">here</Link>
                  </p>
                </div>
              </form>
            </div>
            <div className="modal-footer" style={{borderTop: 0}}></div>
          </div>
        </div>
      </div>
    );
  }
}

Our login page is overriding the form’s submit event and calling Meteor’s loginWithPassword() function to create a session. We’re also showing
authentication errors by modifying the component’s state when there’s an error logging in.

imports/ui/pages/SignupPage.jsx

import React, { Component } from 'react';
 import { withHistory, Link } from 'react-router-dom';
 import { Accounts } from 'meteor/accounts-base';

export default class SignupPage extends Component {
  constructor(props){
    super(props);
    this.state = {
      error: ''
    };
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(e){
    e.preventDefault();
    let name = document.getElementById("signup-name").value;
    let email = document.getElementById("signup-email").value;
    let password = document.getElementById("signup-password").value;
    this.setState({error: "test"});
    Accounts.createUser({email: email, username: name, password: password}, (err) => {
      if(err){
        this.setState({
          error: err.reason
        });
      } else {
        this.props.history.push('/login');
      }
    });
  }

  render(){
    const error = this.state.error;
    return (
      <div className="modal show">
        <div className="modal-dialog">
          <div className="modal-content">
            <div className="modal-header">
              <h1 className="text-center">Sign up</h1>
            </div>
            <div className="modal-body">
              { error.length > 0 ?
                <div className="alert alert-danger fade in">{error}</div>
                :''}
              <form  id="login-form"
                    className="form col-md-12 center-block"
                    onSubmit={this.handleSubmit}>
                <div className="form-group">
                  <input type="text" id="signup-name"
                        className="form-control input-lg" placeholder="name"/>
                </div>
                <div className="form-group">
                  <input type="email" id="signup-email"
                        className="form-control input-lg" placeholder="email"/>
                </div>
                <div className="form-group">
                  <input type="password" id="signup-password"
                        className="form-control input-lg"
                        placeholder="password"/>
                </div>
                <div className="form-group">
                  <input type="submit" id="login-button"
                        className="btn btn-lg btn-primary btn-block"
                        value="Sign Up" />
                </div>
                <div className="form-group">
                  <p className="text-center">
                    Already have an account? Login <Link to="/login">here</Link>
                  </p>
                </div>
              </form>
            </div>
            <div className="modal-footer" style={{borderTop: 0}}></div>
          </div>
        </div>
      </div>
    );
  }
}

Our signup page component is very similar to our login page, only instead of calling loginWithPassword(), we’ll be creating a user with the createUser() function. This will insert a new user account document into our app’s database.

imports/ui/pages/MainPage.jsx

import React, { Component } from 'react';
 import { withHistory, Link } from 'react-router-dom';
 import PropTypes from 'prop-types';

export default class MainPage extends Component {
  constructor(props){
    super(props);
    this.state = {
      username: ''
    };
  }

  render(){
    let currentUser = this.props.currentUser;
    let userDataAvailable = (currentUser !== undefined);
    let loggedIn = (currentUser && userDataAvailable);
    return (
      <div>
        <div className="container">
          <h1 className="text-center">
            { loggedIn ? 'Welcome '+currentUser.username : '' }
          </h1>
        </div>
      </div>
    );
  }
}

MainPage.propTypes = {
  username: React.PropTypes.string
}


imports/ui/containers/MainContainer.jsx

import { createContainer } from 'meteor/react-meteor-data';
import MainPage from '../pages/MainPage.jsx'

export default MainContainer = createContainer(({params}) => {
  const currentUser = Meteor.user();
  return {
    currentUser,
  };
}, MainPage);

The main page is simply showing the user’s name in a “welcome” message. Obviously this isn’t a realistic use case for a Meteor app, but it does show how you can access reactive data sources in your React components by using the createContainer() function.

The last change you will need to make to set up routing in our application will be in client/main.jsx. Instead of rendering the placeholder “test” component, we will be including the renderRoutes() function from our routes file and rendering that in our target div.

client/main.jsx

import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';

// add render routes function
import { renderRoutes } from '../imports/startup/client/routes.jsx'

// render routes after DOM has loaded
Meteor.startup(() => {
  render(renderRoutes(), document.getElementById('target'));
});

We should now have a fully functional Meteor app with user authentication. Try running your app by using the meteor command within your app’s directory in a terminal. Opening localhost:3000 in your browser should redirect you to the app’s login page. If your app is not working for whatever reason, the source code for this tutorial is available here for your reference.

As you can see, setting up user authentication with custom views is entirely possible and fairly straightforward in Meteor when using React and React Router. While there are modules like accounts-ui that you can use to add login and signup views to your app, building out these views yourself will give you the freedom to fully customize what’s being shown to your users.