REST APIs https://vulehuan.com/en/blog/2024/7/rest-apis-simple-communication-for-powerful-apps-668a145df0915ca45912b906.html are like doors to your web application. Just as you lock your house door, you need to secure your API. Here are two simple ways to do it: Whitelisting Websites and Using Tokens.
Imagine you have a party, and you only want your friends to come. You make a list of their names and check it at the door. Whitelisting works the same way for APIs:
require 'sinatra'
ALLOWED_DOMAINS = ['client-1.com', 'client-2.net']
get '/api/orders' do
origin = request.env['HTTP_ORIGIN']
unless ALLOWED_DOMAINS.any? { |domain| origin&.include?(domain) }
halt 403, "Access denied"
end
# Your code
end
request.env['HTTP_ORIGIN']
is not entirely secure on its own:
HTTP_ORIGIN
? HTTP_ORIGIN
is based on the Origin
header sent by the client. This header is meant to indicate where the request originated from.Origin
, can be manipulated by the client. A malicious user could potentially set any value they want for this header.Origin
is still useful in combination with other security measures, particularly for CORS (Cross-Origin Resource Sharing) policies.How to improve security:
Origin
for critical security decisions.Tokens are like secret passwords for your API. Here's how they work:
JWTs are a popular type of token. They're like digital ID cards containing:
How to use JWTs:
By combining whitelisting and JWTs https://github.com/jwt/ruby-jwt, you create two layers of security. It's like having a guard at the door (whitelisting) and checking ID cards inside (JWTs).
Example of a more robust approach in Ruby:
require 'sinatra'
require 'jwt'
SECRET_KEY = 'my_secret_key'
ALLOWED_DOMAINS = ['client-1.com', 'client-2.net']
before do
# Check Origin as a first line of defense
origin = request.env['HTTP_ORIGIN']
unless ALLOWED_DOMAINS.any? { |domain| origin&.include?(domain) }
halt 403, "Access denied"
end
# Verify JWT for more robust security
token = request.env['HTTP_AUTHORIZATION']
begin
@payload = JWT.decode(token, SECRET_KEY, true, algorithm: 'HS256')[0]
rescue JWT::DecodeError
halt 401, "Invalid token"
end
end
get '/api/orders' do
# At this point, we've verified both Origin and JWT
# Your code with @payload['username']
end
Simple to implement, but less secure for user-specific operations.
Example in Ruby:
require 'sinatra'
API_KEYS = ['key1', 'key2', 'key3']
before do
api_key = request.env['HTTP_X_API_KEY']
halt 401, 'Invalid API Key' unless API_KEYS.include?(api_key)
end
get '/api/data' do
"Here's your data!"
end
More complex but very secure and widely used for third-party authentication.
Example using the oauth2 gem:
require 'sinatra'
require 'oauth2'
client = OAuth2::Client.new('client_id', 'client_secret', site: 'https://example.com')
get '/auth' do
redirect client.auth_code.authorize_url(redirect_uri: 'http://localhost:4567/callback')
end
get '/callback' do
token = client.auth_code.get_token(params[:code], redirect_uri: 'http://localhost:4567/callback')
session[:access_token] = token.token
"You're authenticated!"
end
Example:
require 'sinatra'
use Rack::Auth::Basic, "Restricted Area" do |username, password|
username == 'admin' && password == 'secret'
end
get '/api/data' do
"Here's your protected data!"
end
Similar to API keys but more secure.
Example:
require 'sinatra'
require 'openssl'
SECRET_KEY = 'your_secret_key'
def valid_signature?(data, signature)
calculated = OpenSSL::HMAC.hexdigest('SHA256', SECRET_KEY, data)
calculated == signature
end
post '/api/data' do
data = request.body.read
signature = request.env['HTTP_X_SIGNATURE']
halt 401, 'Invalid signature' unless valid_signature?(data, signature)
"Data received and verified!"
end
Each of these methods has its own pros and cons:
The choice depends on your specific needs, such as the type of application, security requirements, and ease of implementation. For many APIs, a combination of these methods can provide robust security.