メインコンテンツまでスキップ

RBAC と JWT 検証で Sinatra API を保護する

このガイドでは、Logto が発行する ロールベースのアクセス制御 (RBAC)JSON Web Token (JWT) を利用して Sinatra API に認可 (Authorization) を実装し、API を安全に保護する方法を説明します。

始める前に

クライアントアプリケーションは Logto から アクセス トークン (Access token) を取得する必要があります。まだクライアント統合を設定していない場合は、React、Vue、Angular などのクライアントフレームワーク向け クイックスタート や、サーバー間アクセス用の マシン間通信 (M2M) ガイド をご覧ください。

このガイドは、Sinatra アプリケーションにおけるこれらのトークンの サーバーサイド検証 に焦点を当てています。

A figure showing the focus of this guide

学べること

  • JWT 検証:アクセス トークン (Access token) を検証し、認証 (Authentication) 情報を抽出する方法
  • ミドルウェア実装:API 保護のための再利用可能なミドルウェアの作成
  • 権限モデル:さまざまな認可 (Authorization) パターンの理解と実装
    • アプリケーション全体のエンドポイント向けグローバル API リソース
    • テナント固有の機能制御のための組織 (Organization) 権限
    • マルチテナントデータアクセスのための組織レベル API リソース
  • RBAC 統合:API エンドポイントでロールベースの権限 (Permission) とスコープ (Scope) を強制する方法

前提条件

  • Ruby の最新安定版がインストールされていること
  • Sinatra および Web API 開発の基礎知識
  • Logto アプリケーションが設定済み(必要に応じて クイックスタート を参照)

権限モデルの概要

保護を実装する前に、アプリケーションアーキテクチャに適した権限モデルを選択してください。これは Logto の 3 つの主要な 認可 (Authorization) シナリオ に対応しています:

グローバル API リソース RBAC
  • ユースケース: アプリケーション全体で共有される API リソースを保護する(組織固有ではない)
  • トークンタイプ: グローバルオーディエンスを持つアクセス トークン
  • 例: パブリック API、コアプロダクトサービス、管理エンドポイント
  • 最適: すべての顧客が利用する API を持つ SaaS プロダクト、テナント分離のないマイクロサービス
  • 詳細: グローバル API リソースの保護

💡 進める前にモデルを選択してください — このガイド全体で選択したアプローチを参照します。

クイック準備手順

Logto リソースと権限の設定

  1. API リソースの作成: コンソール → API リソース にアクセスし、API を登録します(例: https://api.yourapp.com
  2. 権限の定義: read:productswrite:orders などのスコープを追加します – 権限付き API リソースの定義 を参照
  3. グローバルロールの作成: コンソール → ロール にアクセスし、API 権限を含むロールを作成します – グローバルロールの設定 を参照
  4. ロールの割り当て: API アクセスが必要なユーザーまたは M2M アプリケーションにロールを割り当てます
RBAC が初めてですか?:

ロールベースのアクセス制御ガイド からステップバイステップのセットアップ手順を始めましょう。

クライアントアプリケーションの更新

クライアントで適切なスコープをリクエストする:

通常、クライアント設定を次のいずれか、または複数を含めるように更新します:

  • OAuth フローでの scope パラメーター
  • API リソースアクセス用の resource パラメーター
  • 組織コンテキスト用の organization_id
コーディング前に:

テストするユーザーまたは M2M アプリが、API に必要な権限を含む適切なロールまたは組織ロールに割り当てられていることを確認してください。

API プロジェクトの初期化

新しい Sinatra プロジェクトを初期化するには、ディレクトリを作成し、基本的な構造をセットアップします:

mkdir your-api-name
cd your-api-name

Gemfile を作成します:

Gemfile
source 'https://rubygems.org'

gem 'sinatra'

依存関係をインストールします:

bundle install

基本的な Sinatra アプリケーションを作成します:

app.rb
require 'sinatra'
require 'json'

get '/' do
content_type :json
{ message: 'Hello from Sinatra API' }.to_json
end

開発サーバーを起動します:

ruby app.rb
注記:

ルート、ミドルウェア、その他の機能のセットアップ方法については、Sinatra のドキュメントを参照してください。

定数とユーティリティの初期化

トークンの抽出と検証を処理するために、コード内で必要な定数やユーティリティを定義してください。有効なリクエストには、Authorization ヘッダーが Bearer <アクセス トークン (Access token)> の形式で含まれている必要があります。

auth_constants.rb
module AuthConstants
JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks'
ISSUER = 'https://your-tenant.logto.app/oidc'
end
auth_info.rb
class AuthInfo
attr_accessor :sub, :client_id, :organization_id, :scopes, :audience

def initialize(sub, client_id = nil, organization_id = nil, scopes = [], audience = [])
@sub = sub
@client_id = client_id
@organization_id = organization_id
@scopes = scopes
@audience = audience
end

def to_h
{
sub: @sub,
client_id: @client_id,
organization_id: @organization_id,
scopes: @scopes,
audience: @audience
}
end
end
authorization_error.rb
class AuthorizationError < StandardError
attr_reader :status

def initialize(message, status = 403)
super(message)
@status = status
end
end
auth_helpers.rb
module AuthHelpers
def extract_bearer_token(request)
authorization = request.headers['Authorization']

raise AuthorizationError.new('Authorization header is missing', 401) unless authorization
raise AuthorizationError.new('Authorization header must start with "Bearer "', 401) unless authorization.start_with?('Bearer ')

authorization[7..-1] # 'Bearer ' プレフィックスを削除
end
end

Logto テナント情報の取得

Logto が発行したトークンを検証するには、次の値が必要です:

  • JSON Web Key Set (JWKS) URI:JWT 署名を検証するために使用される Logto の公開鍵の URL。
  • 発行者 (Issuer):期待される発行者値(Logto の OIDC URL)。

まず、Logto テナントのエンドポイントを見つけます。これはさまざまな場所で確認できます:

  • Logto コンソールの 設定ドメイン で確認できます。
  • Logto で設定した任意のアプリケーション設定内の 設定エンドポイント & 資格情報 で確認できます。

OpenID Connect ディスカバリーエンドポイントから取得する

これらの値は、Logto の OpenID Connect ディスカバリーエンドポイントから取得できます:

https://<your-logto-endpoint>/oidc/.well-known/openid-configuration

以下はレスポンス例です(他のフィールドは省略しています):

{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}

Logto では JWKS URI や発行者 (Issuer) のカスタマイズができないため、これらの値をコード内にハードコーディングすることも可能です。ただし、将来的に設定が変更された場合のメンテナンス負荷が増加する可能性があるため、本番アプリケーションでは推奨されません。

  • JWKS URI:https://<your-logto-endpoint>/oidc/jwks
  • 発行者 (Issuer):https://<your-logto-endpoint>/oidc

トークンと権限の検証

トークンを抽出し OIDC 設定を取得した後、次の点を検証してください:

  • 署名:JWT は有効であり、Logto(JWKS 経由)によって署名されている必要があります。
  • 発行者 (Issuer):Logto テナントの発行者 (Issuer) と一致している必要があります。
  • オーディエンス (Audience):Logto に登録された API のリソースインジケーター、または該当する場合は組織コンテキストと一致している必要があります。
  • 有効期限:トークンが有効期限切れでないこと。
  • 権限 (スコープ) (Permissions (scopes)):トークンに API / アクションに必要なスコープが含まれている必要があります。スコープは scope クレーム内のスペース区切り文字列です。
  • 組織コンテキスト:組織レベルの API リソースを保護する場合、organization_id クレームを検証してください。

JWT の構造やクレームについて詳しくは JSON Web Token を参照してください。

各権限モデルで確認すべきこと

クレームや検証ルールは権限モデルによって異なります:

  • オーディエンスクレーム (aud): API リソースインジケーター
  • 組織クレーム (organization_id): なし
  • チェックするスコープ(権限) (scope): API リソース権限

非 API 組織権限の場合、組織コンテキストは aud クレーム(例:urn:logto:organization:abc123)で表されます。organization_id クレームは組織レベル API リソーストークンにのみ存在します。

ヒント:

セキュアなマルチテナント API のため、必ず権限(スコープ)とコンテキスト(オーディエンス、組織)の両方を検証してください。

検証ロジックの追加

jwt gem を使用して JWT を検証します。Gemfile に追加してください:

Gemfile
gem 'jwt'
# net-http は Ruby 2.7 以降の標準ライブラリの一部なので、明示的に追加する必要はありません

次に、以下を実行します:

bundle install

まず、JWKS とトークン検証を扱うための共通ユーティリティを追加します:

jwt_validator.rb
require 'jwt'
require 'net/http'
require 'json'

class JwtValidator
include AuthHelpers

def self.fetch_jwks
@jwks ||= begin
uri = URI(AuthConstants::JWKS_URI)
response = Net::HTTP.get_response(uri)
raise AuthorizationError.new('Failed to fetch JWKS', 401) unless response.is_a?(Net::HTTPSuccess)

jwks_data = JSON.parse(response.body)
JWT::JWK::Set.new(jwks_data)
end
end

def self.validate_jwt(token)
jwks = fetch_jwks

# JWT ライブラリに JWKS からアルゴリズム検出を任せる
decoded_token = JWT.decode(token, nil, true, {
iss: AuthConstants::ISSUER,
verify_iss: true,
verify_aud: false, # 権限モデルに基づきオーディエンスは手動で検証します
jwks: jwks
})[0]

verify_payload(decoded_token)
decoded_token
end

def self.create_auth_info(payload)
scopes = payload['scope']&.split(' ') || []
audience = payload['aud'] || []

AuthInfo.new(
payload['sub'],
payload['client_id'],
payload['organization_id'],
scopes,
audience
)
end

def self.verify_payload(payload)
# 権限モデルに基づく検証ロジックをここに実装してください
# この内容は下記の権限モデルセクションで示します
end
end

次に、アクセス トークン (Access token) を検証するミドルウェアを実装します:

auth_middleware.rb
class AuthMiddleware
include AuthHelpers

def initialize(app)
@app = app
end

def call(env)
request = Rack::Request.new(env)

# 特定のルートのみを保護
if request.path.start_with?('/api/protected')
begin
token = extract_bearer_token(request)
decoded_token = JwtValidator.validate_jwt(token)

# 認証情報を env に保存し、汎用的に利用できるようにする
env['auth'] = JwtValidator.create_auth_info(decoded_token)

rescue AuthorizationError => e
return [e.status, { 'Content-Type' => 'application/json' }, [{ error: e.message }.to_json]]
rescue JWT::DecodeError, JWT::VerificationError, JWT::ExpiredSignature => e
return [401, { 'Content-Type' => 'application/json' }, [{ error: 'Invalid token' }.to_json]]
end
end

@app.call(env)
end
end

権限モデルに従い、JwtValidator 内で適切な検証ロジックを実装してください:

jwt_validator.rb
def self.verify_payload(payload)
# audience クレームが API リソースインジケーターと一致するか確認
audiences = payload['aud'] || []
unless audiences.include?('https://your-api-resource-indicator')
raise AuthorizationError.new('Invalid audience')
end

# グローバル API リソースに必要なスコープを確認
required_scopes = ['api:read', 'api:write'] # 実際に必要なスコープに置き換えてください
token_scopes = payload['scope']&.split(' ') || []

unless required_scopes.all? { |scope| token_scopes.include?(scope) }
raise AuthorizationError.new('Insufficient scope')
end
end

ミドルウェアを API に適用する

これで、保護された API ルートにミドルウェアを適用します。

app.rb
require 'sinatra'
require 'json'
require_relative 'auth_middleware'
require_relative 'auth_constants'
require_relative 'auth_info'
require_relative 'authorization_error'
require_relative 'auth_helpers'
require_relative 'jwt_validator'

# ミドルウェアを適用
use AuthMiddleware

get '/api/protected' do
content_type :json

# env から認証情報にアクセス
auth = env['auth']
{ auth: auth.to_h }.to_json
end

# パブリックエンドポイント(ミドルウェアによる保護なし)
get '/' do
content_type :json
{ message: "パブリックエンドポイント" }.to_json
end

保護された API のテスト

アクセス トークン (Access tokens) の取得

クライアントアプリケーションから: クライアント統合を設定している場合、アプリは自動的にトークンを取得できます。アクセス トークン (Access token) を抽出し、API リクエストで使用してください。

curl / Postman でのテスト用:

  1. ユーザートークン: クライアントアプリの開発者ツールを使い、localStorage またはネットワークタブからアクセス トークン (Access token) をコピーします。

  2. マシン間通信トークン: クライアントクレデンシャルフローを使用します。以下は curl を使った非公式な例です:

    curl -X POST https://your-tenant.logto.app/oidc/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "client_id=your-m2m-client-id" \
    -d "client_secret=your-m2m-client-secret" \
    -d "resource=https://your-api-resource-indicator" \
    -d "scope=api:read api:write"

    resourcescope パラメーターは API リソースや権限に応じて調整が必要です。API が組織スコープの場合は organization_id パラメーターも必要になる場合があります。

ヒント:

トークンの内容を確認したい場合は、 JWT デコーダー を使って JWT をデコード・検証できます。

保護されたエンドポイントのテスト

有効なトークンリクエスト
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/protected

期待されるレスポンス:

{
"auth": {
"sub": "user123",
"clientId": "app456",
"organizationId": "org789",
"scopes": ["api:read", "api:write"],
"audience": ["https://your-api-resource-indicator"]
}
}
トークンなし
curl http://localhost:3000/api/protected

期待されるレスポンス (401):

{
"error": "Authorization header is missing"
}
無効なトークン
curl -H "Authorization: Bearer invalid-token" \
http://localhost:3000/api/protected

期待されるレスポンス (401):

{
"error": "Invalid token"
}

権限モデルごとのテスト

グローバルスコープで保護された API のテストシナリオ:

  • 有効なスコープ: 必要な API スコープ(例: api:read, api:write)を含むトークンでテスト
  • スコープ不足: 必要なスコープがない場合は 403 Forbidden を期待
  • 誤ったオーディエンス: オーディエンスが API リソースと一致しない場合は 403 Forbidden を期待
# 必要なスコープがないトークン - 403 を期待
curl -H "Authorization: Bearer token-without-required-scopes" \
http://localhost:3000/api/protected

さらに詳しく

実践でのロールベースのアクセス制御 (RBAC):アプリケーションのための安全な認可 (Authorization) の実装

マルチテナント SaaS アプリケーションの構築:設計から実装までの完全ガイド