はじめに
Rails APIとNext.jsで開発しているときにURL上でIDを平文のまま載せたくない場合の対策になります。
実現させたいこと
具体的には、Rails APIをバックエンドに、Next.jsをフロントエンドとして利用し、
ユーザーがブラウザ上でproduct/1などといったIDを直接露出する形ではなく、暗号化したIDを使ったURL(例: product/暗号化されたID)を用いて、データへのアクセスを行います。
やること
Rails API側
やることとしては共通鍵を設定し、暗号/復号化時にそれを参照するようにしています。
app/controllers/application_controller.rb
class ApplicationController < ActionController::API
before_action :set_encryptor
private
# 共通鍵生成関数
def set_encryptor
# Rails.application.credentials.saltには32文字の適当な英数字を設定
secret_key = Rails.application.credentials.salt
# secret_keyを使用して暗号/復号化のための共通鍵を作成
@encryptor = ActiveSupport::MessageEncryptor.new(secret_key[0, 32])
end
# 暗号化用関数
def encrypt(data)
encrypted_data = @encryptor.encrypt_and_sign(data)
Base64.urlsafe_encode64(encrypted_data)
end
# 復号化用関数
def decrypt(data)
decoded_encrypted_data = Base64.urlsafe_decode64(data)
@encryptor.decrypt_and_verify(decoded_encrypted_data)
end
end
Next.js側
フロント側はもらったものを表示するのみのため実装例は不要と思いますが記載します。
pages/product/[encryptedId].js
import axios from 'axios';
import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';
function ProductPage() {
const { encryptedId } = useRouter().query;
const [product, setProduct] = useState({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!encryptedId) return;
axios.get(`/api/products/${encryptedId}`)
.then(response => {
setProduct(response.data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
});
}, [encryptedId]);
if (loading) return <div>Loading...</div>;
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
</div>
);
}
export default ProductPage;
最後に
今回はRailsとNext.jsでActiveSupport::MessageEncryptorを使用する方法を紹介しましたが、
別のアプローチはたくさんあるのでケースによって検討いただければと思います。