セッション切れ時のフロント側の制御について

開発中の SPA ベースのアプリにおいて「API 呼び出し時、サーバでセッション切れを起こしていた場合は、フロント側でエラーメッセージを表示した後ログイン画面へ遷移させる」という処理が正常に動いていないという、以下のような指摘を受けた。

  • セッション切れの際は認証プロキシがレスポンステータス 302 を返している
  • フロント側で 302 をキャッチした時の分岐が書かれていないため処理が止まっているのでは?

この指摘に従い、以下のような条件分岐を追記したがうまくいかなかった。

const http = axios.create({ baseURL });
http.interceptors.response.use(
  (data) => {
    return Promise.resolve(data);
  },
  (error) => {
    if (error.response) {
      if (error.response.status === 400) {
        badRequest(error.message);

      // 【これの302を追記!】302 も 401 同様に未認証として扱う
      } else if ([401, 302].includes(error.response.status)) {
        unAuthorized();
      } else
        ....
      }
    }
    ...

302 リダイレクト

302 は一時的なページの移動を意味しレスポンスヘッダーのLocationに指定されたページの URL にリダイレクトされる。(今回の案件ではログインページの URL が指定されている)

https://blog.hubspot.jp/http-302

このステータスは、ユーザーがロードしようとしたページやリソースが一時的に別の場所に移されており、移動先への 302 リダイレクトが実行されると発生します。

「未ログイン状態でページにアクセスしようとした場合にログインページに飛ばす」というのは、ログインインターセプターと呼ばれる手法があり、一般的な web アプリケーションの実装でよく用いられる。

リダイレクト処理自体は、例えば Java の Struts2 を使うと次のようなイメージで書ける.

public class FooAction extends ActionSupport implements ServletResponseAware,SessionAware {

    public HttpServletResponse response;

    public void setServletResponse(HttpServletResponse response) {
        this.response = response;
    }

    // ログインページにリダイレクト
    public String gotoLogin() throws Exception{
      this.response.sendRedirect("/login");
    }
}

この時、上記のようにresponse.sendRedirectを用いると、一時的な移転を意味する 302 がレスポンスステータスとして返される。

301 リダイレクト

302 は一時的な移転を意味し、旧ページの評価情報が新ページに引き継がれず SEO の観点ではよくないらしい。情報を引き継ぎたい場合は、恒久的な移転を意味する 301 が返されるように次のよう setStatus()を使いレスポンスステータスを明示的に指定する(これは Java の書き方での話、CGI なんかの場合は以下の内容を単純に標準出力すればよかった記憶がある)。

response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 302
response.setHeader("Location","http://www.foo.com/login");

フロントではリダイレクトを制御できない

では前述の条件分岐では何故うまくいかなかったのか?理由は 302 を受信したタイミングでは、JS は処理を介入させることができないため。ブラウザが 302 を受け付け、その後リダイレクトした結果が JS には返される。

つまり、フロント的にはレスポンスとして JSON を期待して API を実行したのに、セッションが切れていた場合はログインページの HTML が返却されることになる。以下はセッションが切れた状態で GET API を実行した状態。GET 直後にリダイレクトされ、HTML が返されているのが分かる。

ちなみに同じくセッションが切れた状態で PUT API を実行したら、リダイレクト時にnet::ERR_FAILとかいうエラーなり、JS にはNetwork Errorという message プロパティを持った Error オブジェクトが返された。

気になったのはリダイレクトに対してもPUTメソッドが適用されている点。最初のリクエストのメソッドを引き継いじゃっているのが原因ではないかなと思った。

これについては MDN に気になることが書いてあった。

https://developer.mozilla.org/ja/docs/Web/HTTP/Status/302

仕様書ではリダイレクトの際にメソッド (と本文) を変更しないよう要求していますが、すべてのユーザーエージェントが準拠している訳ではありません (まだこの種のバグのあるソフトウェアが見つかるでしょう)。従って、 302 コードは GET または HEAD メソッドへのレスポンスのみに使用し、 POST メソッドのままリダイレクトする場合は代わりに 307 Temporary Redirect (こちらでは明確にメソッドの変更が禁止されている) を使用することが推奨されています。

使用されるメソッドを GET に変更したい場合は、代わりに 303 See Other を使用してください。これは PUT メソッドへのレスポンスとして、アップロードされたリソースではなく「XYZ のアップロードに成功しました」のような確認メッセージを表示したい場合に便利です。

どうしても PUT で要求を受け付けた後にリダイレクトを発生させたい場合は、303 を返すことで GET としてリダイレクトさせれば良いってことかな...

で、どうするか?

サーバサイド的には認証を自前実装しておらず、どこぞの認証プロキシを利用してるらしく、それが 302 を返しているとのことだった。

とりあえず「リクエストヘッダーのContent-Typeapplication/jsonが指定されていた場合は、302 ではなく 401 を返すことが、その認証プロキシで可能なのか?」、もし無理なら「NetworkError 発生時、もしくは、レスポンス結果がログインページの HTML の場合は 302 が発生した」っていう判断をしなければならなさそう.....