Simple Ways to Protect an API: HTTP Basic Authentication and HTTP Token Authentication

6 min read
httpruby

The backend for theScore mobile apps consists of many different web services (Core Sports, Personalization, Push Alerts, etc.). Each service provides its own RESTful API. Some of these services are internal. As such, they only have a very limited number of consumers. Having a limited number of consumers for an API lets you keep the authentication really simple for that API.

For the purposes of this blog post, suppose that we have two services. One that is responsible for detecting alerts (i.e. new goal is scored), and another that is responsible for actually sending those alerts to mobile devices. Let's call these Detector and Sender respectively. Also, suppose that Sender is a web service, and Detector is a background processing system. Further, whenever Detector detects an alert, it uses the API provided by Sender to deliver the alerts.

The internals of the services described above would look like the following in Ruby/Rails:

# Detector Service
class GoalDetector
  def perform(*args)
    # Send the alert upon detection of new alert
    # REF https://github.com/lostisland/faraday
    client = Faraday.new('https://sender.abc.com')
    client.post('/alerts', message: 'Spain Goal')
  end
end

# Sender Service (https://sender.abc.com)
class AlertsController < ActionController::Base
  # POST /alerts
  def create
    # Send an alert with message in params[:message]
  end
end

All is good except that our Sender API is public. Anyone who happens to stumble upon that API can discover this great power to send push alerts to our users. So, we need a way to protect the API from unauthorized access.

HTTP Basic Authentication

This is generally the go-to solution for this problem. Our example above would look like the following with Basic Authentication support:

# Detector Service
class GoalDetector
  def perform(*args)
    # Send the alert upon detection of new alert
    # REF https://github.com/lostisland/faraday
    client = Faraday.new('https://sender.abc.com') do |c|
      c.basic_auth('username', 'password')
    end

    client.post('/alerts', message: 'Spain Goal')
  end
end

# Sender Service (https://sender.abc.com)
class AlertsController < ActionController::Base
  before_action :authenticate

  # POST /alerts
  def create
    # Send an alert with message in params[:message]
  end

  private

  def authenticate
    authenticate_or_request_with_http_basic do |username, password|
      username == 'username' && password == 'password'
    end
  end
end

# NOTE: In real applications, we will not have the
# authentication credentials lying around in code.
# We will store them in external configuration.

Now, the POST request to https://sender.abc.com/alerts would require that an Authorization header is present with the credentials encoded correctly in the value. The header would look like:

# dXNlcm5hbWU6cGFzc3dvcmQ=\n is Base64-encoded value
# of the string username:password
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n

If you don't have the credentials, you won't be able to generate the Authorization header correctly. If you send the wrong value for the header in your request, you will get 401 Unauthorized response back. So, we've now effectively protected the Sender service from unauthorized access.

HTTP Token Authentication

With Basic Authentication, you need to configure both username and password in Sender and Detector services. But, if the password is really strong, then it is a secure credential by itself. In other words, you can think of the password as a secure token. In this case, the username becomes redundant. This is where Token Authentication comes in.

Our code example would look like the following with Token Authentication support:

# Detector Service
class GoalDetector
  def perform(*args)
    # Send the alert upon detection of new alert
    # REF https://github.com/lostisland/faraday
    client = Faraday.new('https://sender.abc.com') do |c|
      c.token_auth('WCZZYjnOQFUYfJIN2ShH1iD24UHo58A6TI')
    end

    client.post('/alerts', message: 'Spain Goal')
  end
end

# Sender Service (https://sender.abc.com)
class AlertsController < ActionController::Base
  before_action :authenticate

  # POST /alerts
  def create
    # Send an alert with message in params[:message]
  end

  private

  def authenticate
    authenticate_or_request_with_http_token do |token, options|
      token == 'WCZZYjnOQFUYfJIN2ShH1iD24UHo58A6TI'
    end
  end
end

# NOTE: In real applications, we will not have the
# authentication credentials lying around in code.
# We will store them in external configuration.

Now, the POST request to https://sender.abc.com/alerts would require that that an Authorization header is present with the correct token in the value. The header would look like:

Authorization: Token token="WCZZYjnOQFUYfJIN2ShH1iD24UHo58A6TI"

If you send the wrong token in the Authorization header, you will get 401 Unauthorized response back. Again, we've protected the API from unauthorized access.

Conclusion

Both HTTP Basic Authentication and HTTP Token Authentication offer really simple solutions to protect an API from unauthorized access. But, with Token Authentication, you will have one less thing to configure in your services and consumers. Because of that, I prefer using Token Authentication.

Having said that, unlike Basic Authentication which is an approved spec, Token Authentication is a draft spec:

Regardless of being a draft spec, Token Authentication is well-supported and really easy-to-use. I don't see any practical reasons not to use it.

NOTE: Both Basic Authentication and Token Authentication are insecure unless used over HTTPS. All the code examples above use HTTPS to make this explicit. We use HTTPS on all of our APIs at theScore.