Sinatra Songs API 教程:使用 Auth0 添加授权保护 CRUD API

作者:API传播员 · 2025-10-22 · 阅读时间:5分钟
本文详细介绍了如何使用Auth0为Sinatra API添加授权功能,包括构建一个名为Sinatra Songs API的歌曲CRUD API,并通过Sinatra框架和Auth0实现API的安全保护。文章涵盖了从项目创建、依赖安装、模型设计到API端点的实现和授权逻辑的添加全过程。

一. 简介

在本教程中,我们将学习如何使用 Auth0Sinatra API 添加授权功能,并保护其端点。我们将构建一个名为 Sinatra Songs API 的歌曲 CRUD API,展示如何通过 Sinatra 框架实现基本 API 功能,同时结合 Auth0 来实现安全性和基于令牌的访问控制。


二. 项目要求

在本项目中,我们将使用以下技术和工具:

  • Ruby(推荐版本 3.0 或更高)
  • Sinatra(轻量级 Ruby Web 框架)
  • Auth0(身份验证和授权服务)
  • Postmancurl(用于测试 API)

三. 构建歌曲 API

1. 创建项目

在终端中创建一个名为 sinatra-auth0-songs-api 的新文件夹,并将其设为当前目录:

mkdir sinatra-auth0-songs-api
cd sinatra-auth0-songs-api

2. 安装 Sinatra

创建 Gemfile 文件,用于管理项目依赖:

# Gemfile
source 'https://rubygems.org'

ruby File.read('.ruby-version').strip

gem 'sinatra', '~> 3.0', '>= 3.0.2'
gem 'puma'

创建 .ruby-version 文件,指定 Ruby 版本:

3.1.2

安装依赖:

bundle install

至此,Sinatra 已成功安装!🎉

3. 创建歌曲模型

在项目中创建 models 文件夹,并添加 song.rb 文件:

# models/song.rb
# frozen_string_literal: true

class Song
  attr_accessor :id, :name, :url

  def initialize(id, name, url)
    @id = id
    @name = name
    @url = url
  end

  def to_json(*_args)
    { id: id, name: name, url: url }.to_json
  end
end

4. 实现 CRUD API

创建 api.rb 文件,作为 API 主入口:

# api.rb
# frozen_string_literal: true

require 'sinatra'
require 'json'

before do
  content_type 'application/json'
end

get '/songs' do
  { todo: :implementation }.to_json
end

get '/songs/:id' do
  { todo: :implementation }.to_json
end

post '/songs' do
  { todo: :implementation }.to_json
end

put '/songs/:id' do
  { todo: :implementation }.to_json
end

delete '/songs/:id' do
  { todo: :implementation }.to_json
end

启动服务器:

ruby api.rb

访问 http://localhost:4567 即可测试 API。

5. 使用 songs.json 填充数据

创建 helpers/songs_helper.rb 文件:

# helpers/songs_helper.rb
# frozen_string_literal: true

require_relative '../models/song'
require 'json'

class SongsHelper
  def self.songs
    filepath = File.join(File.dirname(__FILE__), '../songs.json')
    file = File.read(filepath)
    data = JSON.parse(file)['songs']
    data.map { |song| Song.new(song['id'], song['name'], song['url']) }
  end
end

api.rb 中加载数据:

require_relative 'helpers/songs_helper'

songs ||= SongsHelper.songs

get '/songs' do
  songs.to_json
end

四. 添加授权功能

1. 配置 Auth0

  1. 登录 Auth0 管理控制台
  2. 创建新的 API:

    • 名称:Sinatra Songs API
    • 标识符https://sinatra-auth0-songs-api
    • 签名算法:RS256

记录 标识符域名,稍后使用。

2. 安装依赖

Gemfile 中添加:

gem 'dotenv'
gem 'jwt'

安装依赖:

bundle install

创建 .env 文件:

AUTH0_DOMAIN=your-auth0-domain
AUTH0_AUDIENCE=https://sinatra-auth0-songs-api

3. 验证访问令牌

创建 helpers/auth0_client_helper.rb 文件:

# helpers/auth0_client_helper.rb
# frozen_string_literal: true

require 'jwt'
require 'net/http'

class Auth0ClientHelper
  Error = Struct.new(:message, :status)
  Response = Struct.new(:decoded_token, :error)

  def self.domain_url
    "https://#{ENV['AUTH0_DOMAIN']}/"
  end

  def self.decode_token(token, jwks_hash)
    JWT.decode(token, nil, true, {
      algorithm: 'RS256',
      iss: domain_url,
      verify_iss: true,
      aud: ENV['AUTH0_AUDIENCE'],
      verify_aud: true,
      jwks: { keys: jwks_hash[:keys] }
    })
  end

  def self.get_jwks
    jwks_uri = URI("#{domain_url}.well-known/jwks.json")
    Net::HTTP.get_response(jwks_uri)
  end

  def self.validate_token(token)
    jwks_response = get_jwks
    return Response.new(nil, Error.new('Unable to verify credentials', :internal_server_error)) unless jwks_response.is_a?(Net::HTTPSuccess)

    jwks_hash = JSON.parse(jwks_response.body).transform_keys(&:to_sym)
    decoded_token = decode_token(token, jwks_hash)
    Response.new(decoded_token, nil)
  rescue JWT::VerificationError, JWT::DecodeError
    Response.new(nil, Error.new('Bad credentials', 401))
  end
end

4. 保护 API 端点

api.rb 中添加授权逻辑:

require_relative 'helpers/auth0_client_helper'

helpers do
  def authorize!
    token = token_from_request
    validation_response = Auth0ClientHelper.validate_token(token)
    halt validation_response.error.status, { message: validation_response.error.message }.to_json if validation_response.error
  end

  def token_from_request
    authorization_header = request.env['HTTP_AUTHORIZATION']
    halt 401, { message: 'Authorization header missing' }.to_json unless authorization_header

    schema, token = authorization_header.split
    halt 401, { message: 'Invalid authorization header format' }.to_json unless schema.downcase == 'bearer'

    token
  end
end

before method: %i[post put delete] do
  authorize!
end

五. 总结

通过本文,我们学习了如何:

  • 使用 Sinatra 构建基本 CRUD API
  • 使用 Auth0 实现基于令牌的授权保护
  • 保护 API 的敏感端点

这些步骤为构建安全、可扩展的 Ruby Web API 提供了实践经验。

原文链接: https://auth0.com/blog/add-authorization-to-sinatra-api-using-auth0/