Accounts is Everything Meteor Does Right

Written by Pete Corey on Oct 3, 2016.

When I first came to Meteor, I was immediately amazed by full-stack reactivity. After the novelty of being able to instantly see changes in my database on the client wore off, I started building applications.

Soon, I realized that Meteor had something special.

Meteor’s Special Sauce

A big component of Meteor’s “special sauce” is the Accounts system. The powerful functionality and ease of use provided by the Accounts packages is a perfect example of what makes Meteor amazing.

Does your application need to authenticate users? No problem, just add accounts-password, and include a pre-built template in your application:


{{> loginButtons}}

While the default user interface might not win any design awards, the utility of the Accounts package goes above and beyond anything I’ve ever used in any other platform or framework.

Two simple steps take you from zero to sixty in terms of authentication. Once you’re cruising, you can add a variety of other packages to customize the user experience or to incorporate things like authorization, presence tracking, user management, etc…

Being Opinionated

All of this is possible because Meteor takes an incredibly opinionated view of users and accounts. The Accounts package dictates with complete authority everything from how and where user documents will be stored, all the way through to the how the client will pass credentials up to the server.

All of this is decided and implemented in advance, and for the most part, set in stone. The Accounts package offers a few inroads for customization, but for the most part everything is fixed.

In nearly every scenario I’ve faced in the Real World™ this has been fine. I’ve rarely needed to go above and beyond the functionality provided by the Accounts package.

In the few instances where I did need more than the Accounts package provided, I simply forked the package and customized it to suite my needs.

Phoenix In Comparison

Compare the process of setting up authentication in a Meteor application to the process of setting up authentication in our Phoenix Todos application.

In the literate commits series where we’re converting the Meteor Todos application over the to an Elixir/Phoenix stack, it took four articles to fully set up our authentication system:

Phoenix Todos - The User Model
Phoenix Todos - Back-end Authentication
Phoenix Todos - Transition to Redux
Phoenix Todos - Finishing Authentication

During those four weeks, we manually set up every aspect of our authentication system from designing our user model, setting up our JWT signing procedure, writing back-end routes to handle authentication actions, through to wiring up the front-end to handle calling these routes and persisting the authenticated user client-side.

This is a huge amount of work!

That’s not to say that implementing these things yourself doesn’t come without its benefits. Complete control gives you complete freedom. However, exercising these freedoms can be exhausting.

Final Thoughts

It’s important to remember that this kind of up-front work isn’t exclusive to using Phoenix. This has been my experience with every framework and platform I’ve used to date until I found Meteor.

Some frameworks try to ease some of the burden off of the developer, but none have managed to make my life as pleasant as Meteor.

Once I found Meteor, I immediately became accustomed to authentication being handled for me. Going back to manually implementing things that I want to “just work” feels like going back in time.

I’ve noticed that others feel the same way. When this developer talked about his experiences migrating away from Meteor he noted how he particularly missed Meteor’s robust accounts system.

Based on recent rumblings in the community, Meteor has fallen on hard times. In times like these, it’s important to remember everything the framework does well and balance those with its weaknesses.

Phoenix Todos - Finishing Authentication

This post is written as a set of Literate Commits. The goal of this style is to show you how this program came together from beginning to end.

Each commit in the project is represented by a section of the article. Click each section's header to see the commit on Github, or check out the repository and follow along.

Written by Pete Corey on Sep 28, 2016.

Client-side Validation Bug

You may have noticed that with our previous solution, only server-side errors would show on the sign-up form. Client-side validation was taking place in our onSubmit handler, but errors were never propagating to the UI!

This was happening because we were storing client-side validation errors in the JoinPage component’s state:


this.setState({ errors });

However, our render function was pulling errors out of the props passed into the component by Redux.

Our component didn’t have a single source of truth for the errors array.

The fix to this issue is fairly elegant. We can pull the validation checks out of the onSubmit handler and move them into our signUp action. If we detect any validation issues, we’ll return them to the JoinPage component by dispatching a SIGN_UP_FAILURE action:


if (errors.length) {
  return dispatch(signUpFailure(errors));
}

From our component’s perspective, all errors are seen as server-side errors and render correctly.

web/static/js/actions/index.js

... dispatch(signUpRequest()); + + let errors = []; + if (!email) { + errors.push({ email: "Email required" }); + } + if (!password) { + errors.push({ password: "Password required" }); + } + if (password_confirm !== password) { + errors.push({ password_confirm: "Please confirm your password" }); + } + if (errors.length) { + return Promise.resolve(dispatch(signUpFailure(errors))); + } + return fetch("/api/users", {

web/static/js/pages/AuthPageJoin.jsx

... const password_confirm = this.refs.password_confirm.value; - const errors = {}; - - if (!email) { - errors.email = 'Email required'; - } - if (!password) { - errors.password = 'Password required'; - } - if (password_confirm !== password) { - errors.password_confirm = 'Please confirm your password'; - } - - this.setState({ errors }); - if (Object.keys(errors).length) { - return; - }

Sign-out Actions

Now that we’ve established the pattern our Redux actions and reducers will follow, we can start implementing our other authentication features.

To give users the ability to sign out, we’ll start by creating three new actions: SIGN_OUT_REQUEST, SIGN_OUT_SUCCESS, and SIGN_OUT_FAILURE.

Along with the action creators for each of these actions, we’ll also create an asynchronous action function called signOut which accepts the current user’s JWT as an argument. This function makes a DELETE request to our /api/sessions endpoint, sending the jwt in the "Authorization" header:


return fetch("/api/sessions", {
  method: "delete",
  headers: {
    "Accept": "application/json",
    "Content-Type": "application/json",
    "Authorization": jwt
  }
})

Our SIGN_OUT_SUCCESS reducer clears the user and jwt fields in our application state:


case SIGN_OUT_SUCCESS:
  return Object.assign({}, state, {
    user: undefined,
    jwt: undefined
  });

And the SIGN_OUT_FAILURE resolver will save any errors from the server into errors.

Now that our sign-out actions and resolvers are set, we can wire our App component up to our Redux store with a call to connect, and replace our old Meteor.logout() code with a call to our signOut thunk:


this.props.signOut(this.props.jwt)

With that, authenticated users have the ability to sign out of our application!

web/static/js/actions/index.js

... +export const SIGN_OUT_REQUEST = "SIGN_OUT_REQUEST"; +export const SIGN_OUT_SUCCESS = "SIGN_OUT_SUCCESS"; +export const SIGN_OUT_FAILURE = "SIGN_OUT_FAILURE"; + export function signUpRequest() { ... +export function signOutRequest() { + return { type: SIGN_OUT_REQUEST }; +} + +export function signOutSuccess() { + return { type: SIGN_OUT_SUCCESS }; +} + +export function signOutFailure(errors) { + return { type: SIGN_OUT_FAILURE, errors }; +} + export function signUp(email, password, password_confirm) { ... } + +export function signOut(jwt) { + return (dispatch) => { + dispatch(signOutRequest()); + return fetch("/api/sessions", { + method: "delete", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "Authorization": jwt + } + }) + .then((res) => res.json()) + .then((res) => { + if (res.errors) { + dispatch(signOutFailure(res.errors)); + return false; + } + else { + dispatch(signOutSuccess()); + return true; + } + }); + } +}

web/static/js/layouts/App.jsx

... import Loading from '../components/Loading.jsx'; +import { connect } from "react-redux"; +import { signOut } from "../actions"; ... -export default class App extends React.Component { +class App extends React.Component { constructor(props) { ... logout() { - Meteor.logout(); - - // if we are on a private list, we'll need to go to a public one - if (this.props.params.id) { - const list = Lists.findOne(this.props.params.id); - if (list.userId) { - const publicList = Lists.findOne({ userId: { $exists: false } }); - this.context.router.push(`/lists/${ publicList._id }`{:.language-javascript}); - } - } + this.props.signOut(this.props.jwt) + .then((success) => { + if (success) { + // if we are on a private list, we'll need to go to a public one + if (this.props.params.id) { + const list = Lists.findOne(this.props.params.id); + if (list.userId) { + const publicList = Lists.findOne({ userId: { $exists: false } }); + this.context.router.push(`/lists/${ publicList._id }`{:.language-javascript}); + } + } + } + }); } ... }; + +export default connect( + (state) => state, + (dispatch) => ({ + signOut: (jwt) => { + return dispatch(signOut(jwt)); + } + }) +)(App);

web/static/js/reducers/index.js

... SIGN_UP_FAILURE, + SIGN_OUT_REQUEST, + SIGN_OUT_SUCCESS, + SIGN_OUT_FAILURE, } from "../actions"; ... }); + + case SIGN_OUT_REQUEST: + return state; + case SIGN_OUT_SUCCESS: + return Object.assign({}, state, { + user: undefined, + jwt: undefined + }); + case SIGN_OUT_FAILURE: + return Object.assign({}, state, { + errors: action.errors + }); default:

Persisting Users

Unfortunately, if a user refreshes the page after signing up, they’ll lose their authenticated status. This means a user would have to sign-in every time they load the application.

This issue is caused by the fact that we’re saving the user and jwt objects exclusively in our in-memory application state. When we reload the page, that state is reset.

Thankfully, we can fix this issue fairly quickly.

In our signUp thunk, once we recieve a successful response from the server, we can store the user and jwt objects into local storage.


localStorage.setItem("user", JSON.stringify(res.user));
localStorage.setItem("jwt", res.jwt);

Similarly, when a user signs out we’ll clear these local storage entries:


localStorage.removeItem("user");
localStorage.removeItem("jwt");

Now we can popoulate our initialState with these user and jwt values, if they exist in local storage:


const user = localStorage.getItem("user");
const jwt = localStorage.getItem("jwt");

const initialState = {
  user: user ? JSON.parse(user) : user,
  jwt,
  ...

And now when a authenticated user refreshes the page, they’ll stay authenticated.

web/static/js/actions/index.js

... else { + localStorage.setItem("user", JSON.stringify(res.user)); + localStorage.setItem("jwt", res.jwt); dispatch(signUpSuccess(res.user, res.jwt)); ... else { + localStorage.removeItem("user"); + localStorage.removeItem("jwt"); dispatch(signOutSuccess());

web/static/js/reducers/index.js

... +const user = localStorage.getItem("user"); +const jwt = localStorage.getItem("jwt"); + const initialState = { - user: undefined, - jwt: undefined, + user: user ? JSON.parse(user) : user, + jwt, loading: false,

Sign In Front-end

Finally, we can continue the same pattern we’ve been following and implement our sign-in functionality.

We’ll start by copying over the SignInPage component from our Meteor application. Next, we’ll make three new actions: SIGN_IN_REQUEST, SIGN_IN_SUCCESS, and SIGN_IN_FAILURE.

In addition to our actions, we’ll make an asynchronous action creator that sends a POST request to /api/sessions to initiate a sign-in.

The reducers for our new actions will be identical to our sign-up reducers, so we’ll save some typing and re-use them:


case SIGN_IN_SUCCESS:
case SIGN_UP_SUCCESS:
  return Object.assign({}, state, {
    user: action.user,
    jwt: action.jwt
  });
...

Lastly, we can replace the call to Meteor.loginWithPassword with a call to our signIn helper. If this call is successful, we’ll redirect to /:


this.state.signIn(email, password)
  .then((success) => {
    if (success) {
      this.context.router.push('/');
    }
  });

Otherwise, we’ll render any errors we find in this.props.errors:


const errors = (this.props.errors || []).reduce((errors, error) => {
  return Object.assign(errors, error);
}, {});

And with those changes, a user can now sign up, log out, and sign into our application!

web/static/js/actions/index.js

... +export const SIGN_IN_REQUEST = "SIGN_IN_REQUEST"; +export const SIGN_IN_SUCCESS = "SIGN_IN_SUCCESS"; +export const SIGN_IN_FAILURE = "SIGN_IN_FAILURE"; + export function signUpRequest() { ... +export function signInRequest() { + return { type: SIGN_IN_REQUEST }; +} + +export function signInSuccess() { + return { type: SIGN_IN_SUCCESS }; +} + +export function signInFailure(errors) { + return { type: SIGN_IN_FAILURE, errors }; +} + export function signUp(email, password, password_confirm) { ... } + +export function signIn(email, password) { + return (dispatch) => { + dispatch(signInRequest()); + + let errors = []; + if (!email) { + errors.push({ email: "Email required" }); + } + if (!password) { + errors.push({ password: "Password required" }); + } + if (errors.length) { + return Promise.resolve(dispatch(signInFailure(errors))); + } + + return fetch("/api/sessions", { + method: "post", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, password }) + }) + .then((res) => res.json()) + .then((res) => { + if (res.errors) { + dispatch(signInFailure(res.errors)); + return false; + } + else { + localStorage.setItem("user", JSON.stringify(res.user)); + localStorage.setItem("jwt", res.jwt); + dispatch(signInSuccess(res.user, res.jwt)); + return true; + } + }); + } +}

web/static/js/pages/AuthPageSignIn.jsx

+import React from 'react'; +import AuthPage from './AuthPage.jsx'; +import { Link } from 'react-router'; +import { connect } from "react-redux"; +import { signIn } from "../actions"; + +class SignInPage extends React.Component { + constructor(props) { + super(props); + this.state = { + signIn: props.signIn + }; + this.onSubmit = this.onSubmit.bind(this); + } + + onSubmit(event) { + event.preventDefault(); + const email = this.refs.email.value; + const password = this.refs.password.value; + + this.state.signIn(email, password) + .then((success) => { + if (success) { + this.context.router.push('/'); + } + }); + } + + render() { + const errors = (this.props.errors || []).reduce((errors, error) => { + return Object.assign(errors, error); + }, {}); + const errorMessages = Object.keys(errors).map(key => errors[key]); + const errorClass = key => errors[key] && 'error'; + + const content = ( +
+

Sign In.

+

Signing in allows you to view private lists

+ <form onSubmit={this.onSubmit}> +
+ {errorMessages.map(msg => ( + <div className="list-item" key={msg}>{msg}
+ ))} +
+ <div className={`input-symbol ${errorClass('email')}`{:.language-javascript}}> + + + </div> + <div className={`input-symbol ${errorClass('password')}`{:.language-javascript}}> + + + </div> + + </form> + </div> + ); + + const link = Need an account? Join Now.</Link>; + + return <AuthPage content={content} link={link}/>; + } +} + +SignInPage.contextTypes = { + router: React.PropTypes.object, +}; + +export default connect( + (state) => { + return { + errors: state.errors + } + }, + (dispatch) => { + return { + signIn: (email, password) => { + return dispatch(signIn(email, password)); + } + }; + } +)(SignInPage);

web/static/js/reducers/index.js

... SIGN_OUT_FAILURE, + SIGN_IN_REQUEST, + SIGN_IN_SUCCESS, + SIGN_IN_FAILURE, } from "../actions"; ... switch (action.type) { + case SIGN_IN_REQUEST: case SIGN_UP_REQUEST: return state; + case SIGN_IN_SUCCESS: case SIGN_UP_SUCCESS: ... }); + case SIGN_IN_FAILURE: case SIGN_UP_FAILURE:

web/static/js/routes.jsx

... import AppContainer from './containers/AppContainer.jsx'; +import AuthPageSignIn from './pages/AuthPageSignIn.jsx'; import AuthPageJoin from './pages/AuthPageJoin.jsx'; ... <Route path="/" component={AppContainer}> + <Route path="signin" component={AuthPageSignIn}/> <Route path="join" component={AuthPageJoin}/>

Final Thoughts

Now that we’re getting more comfortable with React, it’s becoming more and more enjoyable to use.

The concept of a single application state, while undeniably weird at first, really simplifies a lot of complexities that can show up in more complicated applications. For example, having a single, canonical errors array that holds any error messages that might currently exist is amazing!

Coming from Blaze, the incredibly explicit data flow in a Redux-style application is comforting. It’s completely clear where each action is initiated and how it effects the application’s state.

Gone are the days of racking your brain trying to conceptualize a tree of reactive updates that brought your application into its current state.

Now that the authentication piece is finished (finally), next week we’ll move onto implementing the meat of our application!

My Kingdom for Transactions

Written by Pete Corey on Sep 26, 2016.

Recently, while reviewing a system I built for a client, I realized how strongly the “decision” to use MongoDB effected the architecture and structure of the system.

Using a better tool for the job would have significantly simplified the architecture of the solution and resulted in a more robust and reliable final product.

The System

The general idea of the system is that there are a set of “entities”. Each entity has a set of actions that can be done on them. For example, let’s imagine that we have a Child entity. Here are a few of the actions that can be taken on our Child:

  • put_to_bed
  • feed_breakfast
  • take_to_school
  • consume_free_time
  • etc…

Each action has two main parts. A validate method, and a do method.

Whenever you execute an action on an entity, the validate function will be called first. If this functions fails in any way, we’ll return the failure to the caller and stop all execution of the action before moving forward.

If validate doesn’t catch any problems, we’ll move on to the do function, which executes the meat of the action.

As an example, our put_to_bed action might look something like this:


put_to_bed: {
    validate({bedId}) {
        check(bedId, String);
    },
    do() {
        Child.update(this._id, {
            $set: { in_bed: true }
        });
        Bed.findOne(bedId).do("set_occupant", { childId: this._id });
    }
}

This seems all well and good. We check that bedId, or the ID of the Bed we’re putting the child into is a String. When we execute the action, we update the current Child document and set in_bed to true. Next, we find the bed we’re putting the child into and set its occupant to the child’s ID.

Broken State

But what happens if the bed already has an occupant?

Calling Bed.findOne(bedId).do(...) will trigger the set_occupant action to be triggered on the bed. It’s valiate function will be called, which might look something like this:


set_occupant: {
    validate({childId}) {
        check(childId, String);
        if (this.occupied) {
            throw new Meteor.Error("occupied");
        }
    },
    ...
}

The set_occupant action on the Bed will fail.

This leaves our system in a broken state. The child claims that it’s in bed (in_bed: true), but the bed is occupied by someone else.

Two Phase Commit Problems

The MongoDB documentation explains that this kind of multi-document transaction-style commit can be accomplished using two phase commits. The idea is that we keep track of our set of database changes as we carry out actions, and undo them if things go wrong.

The example two phase commit described in the documentation updates two documents within the same collection. Unfortunately our problem is a little more complex.

The example holds the IDs of the documents being updated in the transaction’s source and destination fields. Our transactions will update an arbitrary number of documents across any number of collections.

Instead of a single source and destination pair, we would need to maintain a list of affected documents, storing both the documents’ _id and collection:


{
    ...
    documents: [
        {
            collection: "children",
            _id: ...
        },
        {
            collection: "beds",
            _id: ...
        }
    ]
}

If something goes wrong in a two phase commit, any updates that have already been carried out need to be rolled back.

In the example scenario described in the MongoDB documentation, rollbacks are easy. All updates are simple increments ($inc: { balance: value }), and can be undone by decrementing by the same value ($inc: { balance: -value }).

But again, our scenario is more complicated.

Our actions are free to modify their respective documents in any way. This means that we have no natural way of undoing these modifications without either storing more additional data, or adding additional code.

One potential solution would be to store the original, pre-modification document along with the transaction’s _id in the pendingTransactions list:


{
    in_bed: true,
    ...
    pendingTransactions: [
        {
            _id: ...,
            document: {
                in_bed: false,
                ...,
                pendingTransactions: []
            }
        }
    ]
}

In the case of a roll-back, we could replace the entire document with this pre-modification document. The downside of this approach is that it drastically increases the size of our entity documents.

Another approach would be to create a new undo function to go along with each of our actions’ do functions. The undo function would simply undo any operations done by the do function.

This approach is very similar to the migration model used by Active Record and other migration frameworks. The obvious downsides of this approach are that we’re adding a huge amount of extra code to our application.

As my good friend Bret Lowrey says, “Code is like a war - the best code is one never written.”

My Kingdom For Transactions

It’s amazing how much architectural effort needs to be put into creating a functional, but awkward solution to this problem.

Interestingly, this kind of problem isn’t unique to this specific application. Most web applications do some kind of transactional updates against multiple documents across one or more collections.

Many developers just ignore the possibility of mid-transaction failures. If it happens, it happens. We’ll just clean up the database on an ad hoc basis.

And why not? When your alternatives are either doubling the size of your codebase or doubling the size of your database, a little manual labor starts to sound more appealing.

For this particular application, we decided that it would make more sense to invest in heavier upfront validation (via robust validate functions and simulations), rather than implementing a proper two phase commit system.

However, this entire mess could have been completely avoided if we had gone with a database that supported proper transactions.

My kingdom for transactions…