ITエンジニアによるITエンジニアのためのブログ

RailsでAPIキー認証を実装する方法

本記事では、Ruby on RailsでAPIキー認証を実装する堅牢な方法を、コード例を交えてステップバイステップで解説します。

目次

  • APIキー認証とは
  • データベースの準備(テーブルとモデル)
  • 安全なAPIキーの生成
  • リクエストの認証処理
  • IPアドレス制限によるセキュリティ強化
  • まとめ

APIキー認証とは

APIキー認証は、トークンベース認証の一種です。主に、以下のような特徴を持ちます。

  • クライアントアプリケーション自体を識別・認証するために使われます。
  • 一度発行されると有効期限は長く、基本的には手動で無効化(ローテーション)するまで永続します。
  • サーバー間通信や、公開データへのアクセスなど、比較的シンプルな認証が求められるシナリオで利用されます。

たとえば、Google Maps Platform APIを利用する際に発行されるAPIキーなどがこれに該当します。

OAuthとの違い

同じくトークンを用いるOAuthのアクセストークンと混同されがちですが、目的が異なります。OAuthは個別のユーザーを認証・認可するための仕組みであり、アクセストークンは有効期限が短いのが一般的です。一方、APIキーはアプリケーション自体を認証します。

データベースの準備(テーブルとモデル)

まず、APIキーを安全に保存するためのテーブルを作成します。

1. マイグレーションファイルの作成

ターミナルで以下のコマンドを実行します。

rails g migration CreateApiKeys

生成されたマイグレーションファイルを以下のように編集します。

# db/migrate/xxxxxxxxxx_create_api_keys.rb
class CreateApiKeys < ActiveRecord::Migration[7.0]
  def change
    create_table :api_keys do |t|
      t.string :name, null: false
      t.string :token_digest, null: false, index: { unique: true }

      t.timestamps
    end
  end
end

rails db:migrate
  • name: APIキーの用途を識別するための名前です(例: 「外部連携用」)。
  • token_digest: APIキーそのものではなく、ハッシュ化された値を保存します。これにより、データベースが漏洩してもキーの悪用リスクを大幅に低減できます。検索を高速化し、一意性を保証するためにインデックスも設定します。

編集後、データベースにテーブルを作成します。

rails db:migrate

2. モデルの作成

次に、ApiKeyモデルを作成し、認証ロジックを実装します。

# app/models/api_key.rb
class ApiKey < ApplicationRecord
  # 秘密鍵を環境変数から読み込み
  HMAC_SECRET_KEY = ENV.fetch('API_KEY_HMAC_SECRET_KEY')

  # ハッシュ化する前のトークンを一時的に保持するための仮想的な属性
  attr_accessor :token

  # レコード作成前にトークンをハッシュ化する
  before_create :generate_token_hmac_digest

  # トークンによる認証(キーが見つからない場合はnilを返す)
  def self.authenticate_by_token(token)
    authenticate_by_token! token
  rescue ActiveRecord::RecordNotFound
    nil
  end

  # トークンによる認証(キーが見つからない場合は例外を発生させる)
  def self.authenticate_by_token!(token)
    digest = OpenSSL::HMAC.hexdigest('SHA256', HMAC_SECRET_KEY, token)
    find_by!(token_digest: digest)
  end

  private

  def generate_token_hmac_digest
    raise ActiveRecord::RecordInvalid, 'Token is required' unless token.present?

    digest = OpenSSL::HMAC.hexdigest('SHA256', HMAC_SECRET_KEY, token)

    self.token_digest = digest
  end
end

このモデルでは、attr_accessor :tokenでデータベースには保存されない仮想的な属性を用意し、before_createコールバックでtokenの値をハッシュ化してtoken_digestカラムに保存しています。

安全なAPIキーの生成

1. 秘密鍵の生成

APIキーをハッシュ化するための秘密鍵(API_KEY_HMAC_SECRET_KEY)を生成します。Railsコンソールで以下のコマンドを実行してください。

# rails c
irb(main):001:0> SecureRandom.hex(32)
=> "d18a77be318f97c7526398fe96b4ccc9fc6f4a37190c174a8cbc17b5820e2f06"

生成された32バイト(16進数で64文字)の文字列を、.envファイルなどの環境変数として設定します。

API_KEY_HMAC_SECRET_KEY=d18a77be318f97c7526398fe96b4ccc9fc6f4a37190c174a8cbc17b5820e2f06

注意: このキーは絶対にGitなどのバージョン管理に含めないでください。

2. APIキーの発行

クライアントに渡すAPIキー自体も生成します。ここでは、管理者がRailsコンソールから発行する例を示します。

# rails c
irb(main):001:0> token = SecureRandom.base64(30)
=> "utTK6G5XjoXsFBPQNacwtl4X146VjYkUu9D9tj/P"

irb(main):002:0> ApiKey.create(token: token, name: 'My First API Key')
=> #<ApiKey id: 1, name: "My First API Key", ...>

SecureRandom.base64を使用すると、URLセーフな文字で構成され、16進数よりも短い文字列を生成できます。base64キーの文字数はバイト数の4/3となるためバイト数に3の倍数を指定すると、末尾にパディング(=)が付かない綺麗なキーになります。

この操作により、ApiKeyモデルのbefore_createコールバックが実行され、データベースにはハッシュ化されたトークンが保存されます。

リクエストの認証処理

生成したAPIキー(ハッシュ化する前のtoken)を、クライアントはリクエストヘッダーに含めて送信します。

curl -v https://engineerjutsu.com:3000/api/v1/items \
  -H 'Authorization: Bearer utTK6G5XjoXsFBPQNacwtl4X146VjYkUu9D9tj/P'
このリクエストをサーバーサイドで認証するための実装は以下の通りです。

1. Concernの作成

認証ロジックを再利用可能なConcern(モジュール)にまとめます。

# app/controllers/concerns/api_key_authenticatable.rb
module ApiKeyAuthenticatable
  extend ActiveSupport::Concern

  include ActionController::HttpAuthentication::Token::ControllerMethods

  attr_reader :current_api_key

  # APIキーによる認証
  def authenticate_with_api_key!
    authenticate_or_request_with_http_token do |token, _options|
      @current_api_key = ApiKey.authenticate_by_token(token)
    end
  end
end

Rails標準のauthenticate_or_request_with_http_tokenメソッドを利用します。このメソッドは、Authorization: Bearer <token>ヘッダーからトークンを抽出し、ブロック内で認証を行います。認証に成功すると処理を継続し、失敗すると自動的に401 Unauthorizedレスポンスを返します。

2. コントローラーへの適用

作成したConcernをAPIのベースとなるコントローラーにincludeし、before_actionで認証処理を呼び出します。

# app/controllers/api/v1/base_controller.rb
class Api::V1::BaseController < ActionController::API
  include ApiKeyAuthenticatable

  # 継承先のbefore_actionより先に実行するためprepend_before_actionを使用
  prepend_before_action :authenticate_with_api_key!
end

これで、このBaseControllerを継承するすべてのコントローラーで、アクションが実行される前にAPIキー認証が強制されます。

IPアドレス制限によるセキュリティ強化

APIキーは永続的であるため、万が一漏洩した際のリスクを軽減する追加のセキュリティ対策が推奨されます。特にサーバー間通信では、送信元のIPアドレスを制限するのが効果的です。

Railsのルーティングで簡単に設定できます。

# config/routes.rb
Rails.application.routes.draw do
  # ホワイトリストに登録されたIPからのみアクセスを許可
  constraints ->(request) { ENV.fetch('IP_ADDRESS_WHITELIST', '').split(',').include?(request.remote_ip) } do
    namespace :api do
      namespace :v1 do
        # ... APIのエンドポイント ...
      end
    end
  end
end

.envファイルに許可したいIPアドレスをカンマ区切りで設定します。

IP_ADDRESS_WHITELIST=192.0.2.1,198.51.100.10

これで、指定されたIPアドレス以外からのアクセスはルーティングレベルで拒否され、Railsアプリケーションのコントローラーまで到達しません。

もし大量のAPIキーごとに異なるIPアドレスを管理したい場合は、api_keysテーブルにallowed_ip_addressesのようなカラムを追加し、認証ロジック(ApiKeyAuthenticatable)内でIPアドレスの検証を行うのが良いでしょう。

まとめ

この記事では、Railsで安全なAPIキー認証を実装する一連の流れを解説しました。

  • APIキーは直接保存せず、必ずハッシュ化する。
  • 認証ロジックはConcernにまとめて再利用性を高める。
  • IPアドレス制限などの追加のセキュリティ対策を検討する。

これらのポイントを押さえて、堅牢なAPIを構築してください。