rails routes - API route with key in path - model, controller
Rails built in features
API Tokens
class ApiKey < ApplicationRecord belongs_to :user has_secure_token end
API subdomain with key
e.g.
GET api.example.com/v1/xxxxkeyxxx/ GET api.example.com/v1/xxxxkeyxxx/articles POST api.example.com/v1/xxxxkeyxxx/articles
#config/routes.rb Rails.application.routes.draw do # ... namespace :api, path: "", constraints: SubdomainConstraint.new("api"), defaults: { format: 'json' } do namespace :v1 do scope "/:key" do resources :articles, only: [:index, :create] get "/", to: "api_connections#show" post "/", to: "api_connections#show" end end match "*path", to: "v1/base#not_found", via: :all end #...
# config/initializers/subdomain_constraint.rb class SubdomainConstraint def initialize(subdomain = nil) @subdomain = subdomain end # With subdomain 'api', this matches api.example.com and api-staging.example.com. def matches?(req) if @subdomain [@subdomain, "#{@subdomain}-staging"].include? req.subdomain else req.subdomain.present? end end end
# app/controllers/api/v1/base_controller.rb module Api module V1 class BaseController < ActionController::Base InvalidApiKey = Class.new(StandardError) protect_from_forgery with: :null_session respond_to :json before_action :authenticate_api_key, except: :not_found before_action :set_default_response_format rescue_from InvalidApiKey do render json: { error: "Invalid API key"} , status: 401 end attr_reader :current_api_key def not_found render json: { error: "Endpoint not valid"} , status: :not_found end private def set_default_response_format request.format = :json end def authenticate_api_key @current_api_key = ApiKey.find_by(key: params[:key]) || raise(InvalidApiKey) end end end end
one last thing you need middlevare that would transllate all requests to JSON even if content-type header is not present = ConsiderAllRequestsJsonMiddlevare
require "rails_helper" RSpec.describe "Api", type: :request do let(:headers) { {"HTTP_HOST" => "api.example.com"} } # this will fake the subdomain let(:key) { create(:api_key).key } it "GET api.example.com/v1/xxxkeyxxx" do get "/v1/#{key}", headers: headers json = JSON.parse(response.body) expect(response).to be_successful #... end #... it "valid payload should create an article" do params = { fullname: "Viktoria Nikolova" } expect { post "/v1/#{api_connection.key}/articles", params: params.to_json, headers: headers } .to change { Article.count }.by(1) expect(response).to be_successful json = JSON.parse(response.body) expect(json).to match({"result" => "ok"}) end