「認証と決済」——Webサービス開発の中で最も面倒な2つの機能だ。ユーザー管理のミスはセキュリティ事故につながる。決済のミスは金銭トラブルになる。それだけに「外部サービスに任せる」という判断が多い。
私の判断は違った。認証は自前実装、決済はStripeに任せる、という分担にした。その理由と、実際に1日で動かすまでの記録を書く。
認証を自前で実装した理由
認証の外部サービスとしてはAuth0やFirebase Authenticationが有名だ。設定すれば数時間でログイン機能が動く。それでも自前実装を選んだ理由が3つある。
1. 依存コストの問題。Auth0は月1,000MAUを超えると有料になる。コンテンツ販売サイトが軌道に乗ったとき、認証だけで月数万円のコストが発生する可能性がある。FastAPIとJWTの組み合わせなら追加コストはゼロだ。
2. Cookie管理のシンプルさ。1cto.jpの認証は「ログインしてCookieを持っている」だけでいい。SSOも多要素認証も不要だ。Auth0の機能の95%を使わない。
3. 学習コストの逆転。Auth0の設定を覚えるより、JWT認証の仕組みを理解した方が他のプロジェクトで潰しが効く。
実装したのは4つだけ
認証の実装は想像より単純だった。必要な機能を列挙すると:
- パスワードハッシュ化:bcryptで平文パスワードをハッシュに変換
- JWTトークン生成:ログイン成功時に7日間有効なトークンを発行
- Cookieへの保存:HTTPOnlyフラグ付きでブラウザに保存(JSから読めない)
- トークン検証:APIへのリクエスト時にCookieからトークンを取り出して検証
これだけだ。コードにすると150行程度。AIと設計を確認しながら書いたので、ミスは最小限に抑えられた。
実装で最も時間をかけたのは「パスワードの強度チェックをどこでやるか」という判断だった。APIサーバー側でやるか、フロントのJavaScriptでやるか。結論は「フロントで8文字以上チェック、バックエンドではbcryptにそのまま渡す」にした。バックエンドに複雑な検証ロジックを入れると、テストが煩雑になる。
Stripeに任せた決済の設計
決済をStripeにした理由は明確だ。PCI DSS(クレジットカード情報取り扱いのセキュリティ基準)の対応を自前でやることは現実的ではない。Stripeが取り扱うのでこちらのサーバーにカード番号が来ない。
Stripeとの連携で実装したのは3つだ。
Checkout Session生成:「巻を購入する」ボタンが押されたとき、Stripe側の決済ページへリダイレクトするURLを生成する。この処理はサーバー側で行い、APIキーが外部に漏れないようにする。
Webhook受信:Stripeで決済が完了したとき、1cto.jpのサーバーに通知が来る。この通知(Webhook)を受けてDBに購入記録を保存する。
アクセス制御:購入記録をDBで確認して、記事を読めるかどうかを判定する。
設計の核心はWebhookだ。「決済完了→記事が読める」という流れを実現するのはWebhookしかない。Stripeの決済ページでカード情報を入力した後、ユーザーはリダイレクトで戻ってくる。しかしこのリダイレクトはキャンセルされることがある。Webhookは決済が確実に完了したときだけ届くので、こちらをトリガーにするのが正しい設計だ。
1日で動かすために捨てたもの
「1日で実装する」ために意識的に後回しにしたものがある。
メール認証:登録時にメールを送って確認するフロー。今は「メールアドレスを入力したらすぐ登録完了」にした。スパム登録のリスクはあるが、初期段階では実害は少ない。
パスワードリセット:忘れた場合のメール送信フロー。問い合わせがあれば手動対応することにした。
サブスクのWebhook処理:サブスクリプションの継続・解約を自動処理するWebhookは複雑なので、初期は「手動で確認する」運用にした。
捨てる判断はAIと相談しながらやった。「この機能がなかったとき、最悪のケースは何か」という問いを繰り返して優先度を決めた。
*次回は「Webhook署名検証で422エラーが出続けた2時間の記録」*
1日で動いたことの本当の意味
「1日で認証と決済が動いた」という事実は、技術の話だけではない。
AIと設計を確認しながら、判断を積み重ねた1日だった。「何を実装するか」「何を後回しにするか」「どこに時間をかけるか」——AIは実装の速度を上げてくれたが、判断は私がした。「判断と実装を1人で回せる」という体制が、今後の開発の基盤になった。
*シンプルシステム株式会社 代表 伊藤勝彦*