0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js】リダイレクトパラメータを処理する方法(招待リンクログインを例に解説)

0
Posted at

はじめに

招待リンク(例: /invite/abc)をクリックしたユーザーが未ログインだった場合、一度ログインページへ遷移させ、ログイン完了後に元の招待ページへ戻したいという要件はよくあります。

この記事では、redirect クエリパラメータを使用してユーザーの元の場所を復元する方法と、その際に必ず実装すべきセキュリティ対策について解説します。

この記事でわかる・できること

  • ログイン後に元のページ(招待画面など)にユーザーを自動で戻す実装方法
  • 「オープンリダイレクト脆弱性」を防ぐためのセキュリティ対策
  • Firebase Authの signInWithPopupsignInWithRedirect それぞれに適したパラメータ保持の方法

この記事の対象者

  • Next.js (App Router) で認証機能を実装している方
  • ログイン後の画面遷移フローを改善したい方
  • リダイレクト処理のセキュリティリスクについて理解したい方

動作環境・使用するツールや言語

  • Next.js: 13.0以上 (App Router使用)
  • Firebase Auth: v9以上
  • React: 18以上

1. 基本的な実装フロー

招待ページから未ログイン状態でアクセスがあった場合、通常はミドルウェアやガード処理で以下のようにリダイレクトさせます。

https://example.com/login?redirect=/invite/abc

ログインページでは、この redirect パラメータを取得し、認証成功後にそのパスへ遷移させます。

実装コード例

以下は、redirect クエリパラメータを読み取り、ログイン後に元の場所へ戻す基本的な実装です。

src/app/login/page.jsx
"use client";

import { useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { onAuthStateChanged, signInWithPopup, GoogleAuthProvider } from "firebase/auth";
import { auth } from "@/app/lib/firebase";

export default function LoginPage() {
  const router = useRouter();
  const searchParams = useSearchParams();
  
  // URLクエリまたはsessionStorageからリダイレクト先を取得
  const redirectPath = searchParams.get("redirect") || sessionStorage.getItem("postLoginRedirect") || "/menus";

  useEffect(() => {
    // 【セキュリティ対策】アプリ内パスのみ許可する検証関数
    const safeRedirect = (path) => {
      // 1. パスが存在する
      // 2. "/" で始まっている(相対パス)
      // 3. "//" で始まっていない(プロトコル指定の絶対URL回避)
      if (path && path.startsWith("/") && !path.startsWith("//")) {
        return path;
      }
      return "/menus"; // デフォルトの戻り先
    };

    const unsubscribe = onAuthStateChanged(auth, (user) => {
      if (user) {
        // ログイン済みなら安全なパスへリダイレクト
        router.replace(safeRedirect(redirectPath));
        
        // 使用済みのsessionStorageはクリアしておく
        sessionStorage.removeItem("postLoginRedirect");
      }
    });

    return () => unsubscribe();
  }, [router, redirectPath]);

  // ... (ログインボタン等のUI実装)
}

2. 【重要】オープンリダイレクト脆弱性への対策

リダイレクト機能を実装する際、「オープンリダイレクト(Open Redirect)脆弱性」 への対策は必須です。

なぜ危険なのか

対策をしない場合、攻撃者が作成した以下のようなURLをユーザーが踏んでしまう可能性があります。

https://yourapp.com/login?redirect=https://evil-site.com/fake-login

ユーザーは信頼できるドメイン(yourapp.com)でログインしたつもりが、ログイン直後に悪意のあるサイト(evil-site.com)へ転送され、情報を盗まれるフィッシング詐欺の踏み台にされてしまいます。

オープンリダイレクト脆弱性の仕組みと対策

対策方法:絶対URLを拒否する

リダイレクト先は必ずアプリ内のパスに限定する必要があります。

安全なチェック処理
const safeRedirect = (path) => {
  // 外部サイト(http~, //~)への遷移をブロックし、アプリ内ルートのみ許可する
  return (path && path.startsWith("/") && !path.startsWith("//")) 
    ? path 
    : "/menus";
};

このように検証を通すことで、redirect=https://... のような外部URLが渡されても、強制的にデフォルトページ(/menus)へ遷移させることができます。

3. 認証方式によるパラメータ保持の違い

Firebase Authなどを使用する場合、認証方式によってリダイレクトパラメータの扱いが変わります。

パターンA:signInWithPopup(ポップアップ方式)

PCブラウザなどでよく使われる方式です。

  • 特徴: 同一ページ上でポップアップウィンドウが開く
  • メリット: ページ遷移が発生しないため、URLの ?redirect=/invite/abc がそのまま残る
  • 実装: 特別な保存処理は不要。useSearchParams から直接読み取ればOK

パターンB:signInWithRedirect(リダイレクト方式)

モバイルブラウザなどで推奨される方式です。

  • 特徴: Google等の認証ページへ画面全体が遷移し、認証後に戻ってくる
  • 問題点: 外部サイトへ行って戻ってくる間に、URLのクエリパラメータ(?redirect=...)が消失する場合あり
  • 対策: sessionStorage への一時保存が必要

まとめ

招待リンクなどのフローを維持するためには、以下の3点を守って実装しましょう。

  1. 基本フロー: 未ログイン時は ?redirect=... を付与してログインページへ誘導する
  2. セキュリティ: 戻り先は必ず検証し、外部サイトへのリダイレクト(オープンリダイレクト)を防ぐ
  3. 認証方式: signInWithRedirect を使う場合は、パラメータが消えないよう sessionStorage を活用する

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?