はじめに
本記事は錆びかけたRailsの知識を頑張ってアップデートするアドベントカレンダー18日目です。
今回は、Rails7.1で追加されたメソッドであるgenerates_token_for
を使った実装を試してみました。
generates_token_forとは?
トークン(簡易的な鍵のようなもの)を簡単に生成できるメソッドです。
これを使うことで、例えばユーザーのパスワード変更などを行うためのトークンを発行することができます。
generates_token_forを使いダウンロード時にトークンで認証する
背景
Railsガイドの例では、このメソッドを使ってパスワード変更を行うための認証トークンを作っていました。
class User < ActiveRecord::Base
has_secure_password
generates_token_for :password_reset, expires_in: 15.minutes do
# `password_salt`(`has_secure_password`で定義される)は、
# そのパスワードのsaltを返す。パスワードが変更されるとsaltも変更されるので、
# パスワードが変更されるとこのトークンは無効になる。
password_salt&.last(10)
end
end
user = User.first
token = user.generate_token_for(:password_reset)
User.find_by_token_for(:password_reset, token) # => user
user.update!(password: "new password")
User.find_by_token_for(:password_reset, token) # => nil
しかし、Railsでユーザー認証機能といえばdeviseがあるため、パスワード変更をわざわざこちらで作らずともdeviseの用意した方法を使えば良いのでは?と思ってしまいました。
そこで、期間限定のダウンロードリンクを作れるようにできたら面白いかも?と考え、作ってみました。
前提
- deviseでユーザー認証を実装済み
- ユーザーは、自身がすでにアップロードしているファイルを指定して、それを誰でもダウンロードできるリンクを発行できる
- このリンクは期間限定で、リンクを発行する際にその時間を決めることができる
ダウンロードリンクを任意のユーザーが作成できるようにするのはリスクがあります。こちらの機能はアプリケーションの管理者など限られた人が利用する想定です。
実装
まず、ユーザーがファイルと有効期限を指定してリンクを生成できるようにし、次にそのリンクを介してファイルがダウンロードされる際に期限をチェックできるようにします。
まず、DownloadLinkモデルを作成し、それにexpires_at
とfile_path
カラムを持たせます。file_pathはダウンロードさせたいファイルのパスを保存します。
モデル
class DownloadLink < ApplicationRecord
belongs_to :user
generates_token_for :access, expires_in: 15.minutes
def expired?
Time.current > expires_at
end
end
コントローラー
class DownloadLinksController < ApplicationController
before_action :authenticate_user!
def new
@download_link = current_user.download_links.new
end
def create
@download_link = current_user.download_links.new(download_link_params)
if @download_link.save
token = @download_link.generate_token_for(:access)
session[:download_token] = token
redirect_to @download_link, notice: "Download link created successfully."
else
render :new
end
end
def show
@download_link = DownloadLink.find(params[:id])
@token = session[:download_token]
end
def download
@download_link = DownloadLink.find_by_token_for(:access, params[:token])
if @download_link.nil? || @download_link.expired?
redirect_to root_path, alert: "This download link has expired or is invalid."
else
send_file @download_link.file_path
end
end
private
def download_link_params
params.require(:download_link).permit(:expires_at, :file_path)
end
end
ルーティング
resources :download_links, only: [:new, :create, :show]
get '/download/:token', to: 'download_links#download', as: 'download'
使い方
ユーザーはdownload_links#newで期間限定のダウンロードリンクについての情報を入力します。ダウンロードリンクや、ダウンロードできる期限を設定できます。
その後はshowにリダイレクトされ、ダウンロード用のリンク(トークン付き)が表示されます。
表示されたトークンを含むURLでリクエストすることでdownload_links#downloadが動き、トークンの検証が成功すればダウンロードが開始されます。