How to Configure Your First Rails REST API
Creating Your First Rails REST API
Most services these days who offer an API do so through a RESTful interface. Any time an API client wants to interact with an API (e.g. Stripe, Twitter, Twilio), it makes a REST request to the server. This request is made using one of the standard HTTP verbs (GET, POST, PATCH, PUT, DELETE, etc.) to a particular route (e.g. https://my-app.com/appointments/:id
), and the server, specifically its controllers, issues a response.
Normally on a Rails application, the controllers respond to one of those RESTful requests with a view (an .html.erb
template, say). This is fine in the standard rails app MVC context, in which your views are directly tied to your model and controller logic in the same app. In an API app, however, the client is no longer in the same app, but instead in a front-end web application (Angular, EmberJS, etc.), a mobile phone app, or even another server that needs to make a request to your server to obtain or modify data.
For this to work, you need to configure your controllers to respond to a request using a standard data format such as JSON or XML. APIs most commonly use JSON these days, but XML has its advantages depending on your platform.
Respond to JSON
In this example we’ll respond to client requests using JSON. The first step is to replace your render/redirect functions with render: json. Compared to outputting HTML, this is structured data available in a universal format.
Normally, a method in your controller may look something like this:
class PostsController < ApplicationController
...
def index
@posts = Post.all
end
...
end
This would render an index.html.erb
template and pass along the @posts instance variable. In our API, we're going to instead respond with JSON. This then looks like the following (we'll get to the new class it inherits from, ApiController, in a moment):
class PostsController < ApiController
...
def index
posts = Post.all
render json: {status: 'SUCCESS', message: 'Loaded all posts', data: posts}, status: :ok
end
...
end
Notice that the posts variable does not have an @ in front of it, because we're not passing this variable to the view, but directly into our JSON rendering. We can put whatever JSON we want in the curly braces, but it is a standard practice to indicate a status, a message, and the data itself. The last argument is the HTTP status code (in this case, :ok
, which yields 200). Note that we are putting an ActiveRecord
object (posts) into the JSON rendering directly without serializing it! Later versions of Rails happen to be clever enough to automatically serialize for us.
If it’s data from one of your own classes and not ActiveRecord
, you’re going to have to turn the object into a Ruby hash in your class.
What is the process of “opening up an API” to the world?
"Opening up an API" is a commonly used phrase but kind of misleading. Your API routes are still routes on your server just like any other routes. The difference is, you’re advertising that it’s open for use beyond just your company or project, and (hopefully) are implementing an authentication/authorization framework for these users.
All that said, it’s best practice to generate a set of routes specifically for your API. Why? You’ll probably want to scope out different sets of behaviors and privileges on your API users, and have version control for your API.
Create API Routes
#config.rb
...
namespace :api do
namespace :v1 do
resources :posts
end
end
...
The nested namespace blocks will automatically form a /api/v1
path preceding the resources that it contains. In this case the path to the controller action above becomes GET /api/v1/posts
Turn off forgery protection
You may notice that when you generate a new Rails app you see a method that looks like this:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
...
end
This protect_from_forgery
method is to prevent cookie attacks. In an API, this is less relevant because you are not utilizing cookies and Javascript; and each session only lasts as long as the request takes to complete.
Thus, we create a separate API controller that's only for API routes:
#app/controllers/api_controller.rb
class ApiController < ApplicationController
skip_before_action :verify_authenticity_token
end
The skip filter above makes sure that the Rails server isn't left searching for the token for forgery protection. For security reasons however, ONLY ALLOW API CONTROLLERS TO INHERIT FROM THIS CONTROLLER. All other controllers that render traditional .html.erb
templates from your app/views folder should inherit from your ApplicationController
class.
Replace Devise routes and classes
For most Rails apps, Devise is the authentication framework of choice. Devise automatically creates a set of routes for authentication. This is great for a monolithic app, but it’s not suitable for an API, because again, we’re not utilizing cookies, and don’t really sign in and sign out people in the way that it would typically work with a web browser.
So what do we do? Use tokens! I normally prefer to use JWTs, but for now we’ll demonstrate the use of simple tokens to authenticate users.
Token-based authentication
Simple tokens work as an authentication method by representing a user who is trying to log in. They are typically a better choice than Basic Auth (simply using username and password) because it gives extra flexibility for the server to provide or revoke access, without requiring changes to the username/password. Any time access controls need to be changed, the token can simply be changed instead. So finally, how does the authentication work?
-
Simply, a user has a unique token stored in the server's database.
-
The token is presented typically in the header of the HTTP request, and the server reads it.
-
The server tries to match the token with a user in the database. If it matches, the user is authenticated. If not, it sends the user a 401: Unauthorized HTTP code and doesn't allow the controller action to proceed.
Create tokens
For simple tokens, I prefer to use the has_secure_token
gem. Follow the installation instructions to generate 24 character tokens automatically every time a new record in your User model is created. After running the migration, you'll notice that there is a :token
attribute on your User model.
Use token in client request
Now that you have the token, you should place it in the header of your HTTP request from your client. The client is the web app, mobile app, or any other consumer of your API who is making a request to your server. Here's a sample request from the client, written in CURL notation:
curl -X GET -H "X-USER-TOKEN: 07906c47276da7c016c8b8g3" -H "Content-Type: application/json" "https://your-domain.com/api/v1/posts"
The X-USER-TOKEN
parameter is the key of our token. You can name this what you want, but I've put in an X- prefix to indicate that it is a non-standard HTTP header. The Content-Type declaration is also important for telling the server that you're using JSON is the body of your response (as opposed to XML, etc.).
Matching the user and token for authentication
Next we create a method in our ApiController
that attempts to match the token to a user, or otherwise unauthorizes the request.
#app/controllers/api_controller.rb
class ApiController < ApplicationController
...
private
def authenticate_user
user_token = request.headers['X-USER-TOKEN']
if user_token
@user = User.find_by_token(user_token)
#Unauthorize if a user object is not returned
if @user.nil?
return unauthorize
end
else
return unauthorize
end
end
def unauthorize
head status: :unauthorized
return false
end
end
As you can see, we extract the token from the header, and query the user's table to see if a user, who contains the submitted token, exists. If either a user does not exist or a token is not provided, it runs the unauthorize method. If you're not familiar with the head method, it's a simple method provided by Rails to simply send an HTTP status code (in this case 401: unauthorized), without giving a full bodied JSON response (we could've done a render: json method like at the top of this article if we wanted to).
Putting in the token authentication feature
The last step is to make sure that the actions of every controller that inherits from the ApiController
actually authenticates! We do this by putting a before filter at the top of the ApiController
:
#app/controllers/api_controller.rb
class ApiController < ApplicationController
before_action :authenticate_user
...
end
CORS
Unlike in a regular app; where the requests are taking place on the same domain as the server; it’s possible that your users, your mobile phones, etc., are all coming from different domains (origins). To allow for this behavior to happen, you need to install the Rack CORS gem and follow the instructions there.
And just like that, Voila! You’re set up for a Rails API!
Hi!
How can I rewrite resource.find_by_token(‘ABC’) to avoid deprecation warning?
DEPRECATION WARNING: This feature will be removed in the next major release.
Use resource.find_by(token: ‘ABC’) to avoid deprecation warning.
This just helps when token is an attribute of Class.
In my case token is a method and I found this way:
UPDATED!
Where do the Post Controller and the Api Controller intersect? If I simply follow this implementation, I have a Posts controller that always renders the JSON without token authentication.
Brian thank you for pointing this out. Indeed, the PostController should inherit from the ApiController, and I’ve edited the article to reflect this. Thanks!
Thanks a lot! … may i ask you why you prefer jwt in authentication? i am building an app and trying to figure out what is better jwt or rails basic token authentication?
Hi Jihan, great question. I prefer JWT because 1) it’s a worldwide standard for token auth, 2) It has several encryption algorithms, and 3) You can actually encode data into the token that can be decoded into JSON, such as the user’s information.