Yodlee and Rails Implementation, Part 4: User Management

In our previous post, we got to a point where we can retrieve login forms for banks. In order to use those login forms and create accounts for users, we need to first set up users on Yodlee. That is what this tutorial is about.

Usernames and Passwords

Every user in your app needs a Yodlee username and Yodlee password.

As long as you are using Yodlee sandbox mode, you cannot create new users with usernames and passwords. You must use the five pre-created users that Yodlee creates for you. In production mode, we do create user accounts on Yodlee, one for each user of our app.

In production mode, what values do we use as the username and password? If we use the user's email address as the user's Yodlee username, things will break down should the user ever update his email address. (Of course, we could update his Yodlee profile, but that is another API call to keep track of.) I prefer to generate internal usernames and passwords for users. This ensures I never need to worry about changes. Let's get started.

I am assuming that your app already has a User model. Let's add fields to the User model to store the user's Yodlee username and password.

rails generate migration add_yodlee_to_users yodlee_username yodlee_password
rake db:migrate

In the user model, let's add an after_create callback method to set the user's Yodlee username and Yodlee password.

class User < ActiveRecord::Base
  after_create :set_yodlee_credentials

  def set_yodlee_credentials
    if Yodlee::Config.register_users
      self.yodlee_username = "user#{id}@your-app-name.com"
      self.yodlee_password = Yodlee::Misc.password_generator
      save!
    end
  end

end

Let's discuss that.

One of the configuration values we set in the first post of the series was "register_users", which defined whether or not users should be automatically registered on Yodlee when they join your app. This is set to true for production mode only, as in sandbox mode you cannot create users.

So in the case where "register_users" is true, this method will set an appropriate username and email. For the username, we simply use a fake email address with the user's ID value. Because every user ID value is unique, these will always be unique values. For the password, we call Yodlee::Misc.password_generator, a method that we will create now.

We need to use a custom password generator rather then rely on something like SecureRandom.hex because a Yodlee password requirement is that passwords can not have the same character repeat three times consecutively. SecureRandom.hex commonly produces strings like "66a101cd5138fff79cf7d6f9cb4b05ea", which fail that requirement.

Our password generator will create 32-character long strings of gibberish, but will check that no character repeats at all.

app/models/yodlee/misc.rb
module Yodlee
  class Misc

    def self.password_generator
      chars = 'abcdefghijklmnopqrstuvwxyz1234567890'
      password = chars.last(10).split(//).sample
      begin
        char = chars[rand(chars.size)]
        password << char if password[-1] != char
      end while password.length < 32
      password
    end

  end
end

So our users will now be auto-populated with internal Yodlee usernames and passwords. It's time to actually create those users on Yodlee.

Tip: Remember that in Yodlee sandbox mode you must populate the usernames and passwords manually in the console using the five pre-made usernames and passwords Yodlee gives you.

A side point: as you can see, we are storing Yodlee passwords in the database in plain text. Yodlee presents a bit of a security challenge, as we need the user's Yodlee password readable and handy so we can interact with Yodlee on behalf of the user. This makes one-way encryption impossible. We can use two-way encryption, but any hacker with access to your database will have access to your code and will be able to decrypt the Yodlee passwords. On the one hand, this is a little concerning. On the other, storing a password like this isn't much different from storing a non-expiring OAuth token, like Twitter's API gives. If you have thoughts on this, please share in the comments section.

Joining and Leaving Yodlee

Following the pattern we discussed in the previous post, let's make a Yodlee::User class to wrap user instances with additional Yodlee methods.

app/models/yodlee/user.rb
module Yodlee
  class User < Base

    attr_reader :user

    def initialize user
      @user = user
    end

  end
end

The first API call to add is the register call.

app/models/yodlee/user.rb
def register
  response = query({
    :endpoint => '/jsonsdk/UserRegistration/register3',
    :method => :POST,
    :params => {
      :cobSessionToken => cobrand_token,
      :'userCredentials.loginName' => user.yodlee_username,
      :'userCredentials.password' => user.yodlee_password,
      :'userCredentials.objectInstanceType' => 'com.yodlee.ext.login.PasswordCredentials',
      :'userProfile.emailAddress' => user.yodlee_username
    }
  })
  @token = response.userContext.conversationCredentials.sessionToken
end

It's pretty straightforward. We create a user on Yodlee by passing in a new username and password, along with an email address. Because the username is already in an email address format, we can re-use the username as the email address. The API call returns a response. We extract the user's session token from the response and save it to the @token instance variable.

Next, let's add the API call to login a user that is already registered.

app/models/yodlee/user.rb
def login
  response = query({
    :endpoint => '/authenticate/login',
    :method => :POST,
    :params => {
      :cobSessionToken => cobrand_token,
      :login => user.yodlee_username,
      :password => user.yodlee_password
    }
  })
  @token = response.userContext.conversationCredentials.sessionToken
end

Similar to the previous API call, we simply pass in a username and password, and we receive a response containing the user's session token, which we save to the @token instance variable.

Being that we are saving the session token to @token, let's add a convenience method named token which will return the token value if it is set, or login the user and get a new token if the token value is not yet set.

app/models/yodlee/user.rb
def token
  @token ||= login
end

Now anytime we need this user's token, we simply call the token method.

Last, let's add an API call for deleting the user from Yodlee.

app/models/yodlee/user.rb
def destroy
  response = query({
    :endpoint => '/jsonsdk/UserRegistration/unregister',
    :method => :POST,
    :params => {
      :cobSessionToken => cobrand_token,
      :userSessionToken => token
    }
  })
  @token = nil
end

The complete Yodlee::User class should look like this.

app/models/yodlee/user.rb
module Yodlee
  class User < Base

    attr_reader :user

    def initialize user
      @user = user
    end

    def token
      @token ||= login
    end

    def register
      response = query({
        :endpoint => '/jsonsdk/UserRegistration/register3',
        :method => :POST,
        :params => {
          :cobSessionToken => cobrand_token,
          :'userCredentials.loginName' => user.yodlee_username,
          :'userCredentials.password' => user.yodlee_password,
          :'userCredentials.objectInstanceType' => 'com.yodlee.ext.login.PasswordCredentials',
          :'userProfile.emailAddress' => user.yodlee_username
        }
      })
      @token = response.userContext.conversationCredentials.sessionToken
    end

    def login
      response = query({
        :endpoint => '/authenticate/login',
        :method => :POST,
        :params => {
          :cobSessionToken => cobrand_token,
          :login => user.yodlee_username,
          :password => user.yodlee_password
        }
      })
      @token = response.userContext.conversationCredentials.sessionToken
    end

    def destroy
      response = query({
        :endpoint => '/jsonsdk/UserRegistration/unregister',
        :method => :POST,
        :params => {
          :cobSessionToken => cobrand_token,
          :userSessionToken => token
        }
      })
      @token = nil
    end

  end
end

Now that's we've create a Yodlee::User class to wrap the ActiveRecord user instances, it's time to complete the pattern by adding a yodlee method to the User ActiveRecord model.

app/models/user.rb
class User < ActiveRecord::Base

  def yodlee
    @yodlee ||= Yodlee::User.new(self)
  end

end

We can now do things like:

reload!
user = User.first
user.yodlee.register # => add to Yodlee (disabled on sandbox)
user.yodlee.login # => login and get a Yodlee session token
user.yodlee.destroy # => delete from Yodlee

Auto-Registration and Auto-Destruction

When a user joins or leaves our app, we should automatically add or remove that user from Yodlee.

One way to do so is to use callbacks in the ActiveRecord User model.

app/models/user.rb
class User < ActiveRecord::Base

   # ... other methods omitted ... 
  after_create :add_to_yodlee
  before_destroy :remove_from_yodlee

  def add_to_yodlee
    yodlee.register if Yodlee::Config.register_users
  end

  def remove_from_yodlee
    yodlee.destroy if Yodlee::Config.register_users
  end

end

While this is a perfectly legitimate way of doing things, it does couple the User and Yodlee::User classes more than I would like. As your app grows with functionality, these callbacks may get in the way. For that reason, I prefer to do the Yodlee registration in the UsersController instead.

app/controllers/users_controller.rb
class UsersController < ApplicationController

  # ... most actions omitted ...

  def create
    @user = User.new(user_params)
    if @user.create
      session[:user_id] = @user.id
      @user.yodlee.register if Yodlee::Config.register_users
      redirect_to root_path
    else
      render :new
    end
  end

  def destroy
    @user = current_user
    @user.yodlee.destroy if Yodlee::Config.register_users
    @user.destroy
    reset_session
    redirect_to root_path
  end

end

Wrapping Up

Please take a moment to check your current code from this tutorial against these gists, to ensure you're up-to-date.

Coming Up Next

Now that we have users all figured out, it's time to add bank accounts for our users. In the next tutorial, we will explore submitting accounts for data scraping, and retrieving the transaction data.