(You can view the complete sample app on Github if you have trouble following along here.)
Our sample app is comprised of two Ruby source files:
evernote_config.rb, which contains a few bits of configuration information.
en_oauth.rb, the application source code.
evernote_config.rb will look something like this:
require statements at the top are for the Ruby OAuth gem, followed by the
evernote_oauth gem. This gem depends on the
oauth gem and implements the entire Evernote Cloud API, plus additional OAuth-related functionality.
Below that, we have our Consumer Key and Consumer Secret values (which should be set to the corresponding parts of your Evernote Cloud API Key).
Finally, we have a boolean variable
SANDBOX which controls whether our app will connect to the Sandbox development server. If false, our app would connect to the production Evernote service.
Our App’s Components
en_oauth.rb, we have a few different pieces of our application: the
before filter, the
helpers functions, our
get routes and a pair of simple ERB templates.
First, let’s cover what I’m lovingly calling “the preamble”:
First, we include the Sinatra gem and enable cookie-based sessions (part of Sinatra). Second, we include the current directory in the
$LOAD_PATH (so we can load our configuration file). Finally, we load said configuration file. Pretty straightforward.
Now, on with the meat of the app…
This filter runs when the app is initialized and verifies that the
OAUTH_CONSUMER_SECRET constants have been set in
evernote_config.rb. If either constant is initialized to empty string, the error message is displayed:
A collection of plain Ruby functions that will be used in our application. Many of them simply represent variables that we’ve abstracted out of the routes portion of the application. As it happens, this section also contains the calls to the Evernote Cloud API. Let’s quickly look at each of these:
auth_token stores the authentication token we’ll be using to make calls against the Evernote Cloud API. Because our web app is necessarily stateless, this value is stored in Sinatra's
client represents the instance of
EvernoteOAuth::Client. It is instantiated using
auth_token (if we have one),
note_store are the two services of the Evernote Cloud API: the former deals with user and account information, the latter with the user’s notes, notebooks, etc.
en_user represents a single Evernote
User instance retrieved from the Evernote Cloud API.
notebooks contains a collection of
Notebook objects as returned by the Evernote Cloud API when
NoteStore.listNotebooks() is called.
total_note_count is the only function in our app that performs logic beyond simply returning a value and deserves a little more explanation…
We begin with
filter, a new (empty) instance of
NoteFilter. Since we want to query the entire account, we won’t be adding any additional filtering criteria (see the full
NoteFilter definition here).
Next, we call
findNoteCounts, passing our
filter as parameters (the
false parameter indicates that we don’t want notes from the account’s Trash container).
findNoteCounts returns an instance of
NoteCollectionCounts — a hash of notebook GUIDs mapped to the number of notes in the corresponding notebook. Iterating over all of our notebooks, we ask
counts (our instance of
NoteCollectionCounts) for the number of notes in that notebook—using its GUID—and return the total of these counts.
Each instance of
get in our app represents an HTTP request and a corresponding URL pattern. Our application has a total of seven routes. Four of them deal directly with our OAuth implementation and will be discussed in the next section. The remaining three are:
The root of our site, this route simply displays the
index template (defined as
@@index at the end of
en_auth.rb) and prompts the user to begin the OAuth authentication process.
In this route, we first clear the
session object (which is used to track the user’s progress and state across a necessarily stateless web interaction), then redirect the user to
/ so they can begin the process again.
This method collects various information about the user’s account (using methods defined in the
helpers section of our app), adds them to the current
session object and renders the
index template. If there are any problems, the
rescue block of our code is triggered and the
@last_error variable is populated with the cause of the error and is sent to the
error template (also defined at the end of
As with most OAuth flows, our authentication takes places over a series of steps:
- Retrieve a request token from the Evernote Cloud API. This token is used to request an authentication token in the next step.
- Using the request token, send the user to the Evernote site where they will authenticate with their login credentials and authorize our application to access their account.
- The Evernote website will redirect the user back to our defined callback URL and append, among other things, the authentication token we will use when making calls against the Evernote Cloud API.
Here, we ask the Evernote Cloud API for our temporary request token.
To start, we take the current URL as defined by the
url member, remove the
requesttoken portion and replace it with
callback to build our callback URL. Then, we call
client.authentication_request_token to request our token (and sending the
callback_url variable to tell the Evernote Cloud API where to send the response. If all of this goes well, the
session[:request_token] is populated with the token value and the user is scuttled off to
Once we verify that
session[:request_token] exists, we grab the
authorize_url from the request token and send the user there where they’ll be prompted to login with their Evernote account credentials and authorize our application to access their account. If
session[:requesttoken] is not set, the user will see an error and be prompted to start the process over.
After successfully authenticating with Evernote and authorizing our app, they’re sent to
/callback (which we defined during the
When the user is sent back to our callback URL, we verify that either:
oauth_verifier parameter is set in the URL.
request_token value is already present in the
If one of these two conditions is true, we include the
oauth_verifier parameter in our
session. If neither of these conditions is true, the user is shown an error.
Next, we extract the access token (used to make API calls) from the and add it to
session. If it’s not there, an error is displayed. If it is, we send the user to the
/list route we defined earlier. Assuming everything worked as advertised, the user will see their username, number of notes in their account and the name of each notebook in their account.
Our app utilizes two ERB templates:
Clearly, this is a very simple template. First, we allow the user to authenticate with the Evernote Cloud API. This is displayed no matter what. If
session[:notebooks] is defined, we can be reasonably sure that the user’s data has been retrieved from the Evernote Cloud API. Then we display each piece of data.
Even simpler than
error, which displays the value of
@last_error and prompts the user to reset their session and begin the process again.