コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

認証が必要なページにおける403/401と404の使い分けに関してざっくりまとめる

認証エラーだから401を返してあげるのがよいのか、悪いのかをざっくりまとめる

TL;DR

  • 順当に作る場合は認証を終えていないと見れないページはHTTPステータスコードは403(認可における不足の場合は401)のレスポンスコードを返す
  • ただし認証をした人だけ認識できるページの場合は認証できないときのエラーのHTTPステータスコードは404とする場合もある
    • ページとして存在することを認知されたくない場合がこれに該当する

そもそもHTTPステータスコードの403/401と404とは

ざっくり表現すると404はリソースが存在しないことを示し、403/401はリソースへのアクセスを行うことができない旨を示すものとなる。

404

404 は標準的なレスポンスコードの1つで、リクエストされたリソースがサーバー上で見つからなかったことを表します。 - 404 | MDN

403

HTTP の 403 Forbidden クライアントエラーレスポンスコードは、サーバーがリクエストを理解したものの、認証が拒否されたことを示します。 - 403 Forbidden - HTTP | MDN

401

HTTP 401 Unauthorized は、有効な認証資格が不足していることによりリクエストが適用されないことを示すクライアントエラーのレスポンスコードです - 401 Unauthorized - HTTP | MDN

ログインが必要なページに未ログインでアクセスしたときの処理

要ログインのページを作っている場合、そのページにアクセスする際にログイン情報がないと閲覧不能な場合は、前提となる説明のステータスコードを鑑みた場合だと「ログイン、すなわち認証を済ませていないユーザからのアクセスのため、認証が拒否されている状態なので403」と考えることができる。そのためログインが必要なページに未ログインでアクセスすると403ページを出す、というような作りをするケースがある。

認証している人だけに知らせたいページは存在を教えない

ただし、403や401は「このページは認証をしないといけないページです」というのを明示的に表現してしまう。例えば、以下のようなURLのパターンがあるとする。

このURLにおいて、https://example.com/secret/ 配下はログインが必要なページとなっており、この中で実際に存在するページは https://example.com/secret/page/xiqaapes/ とした場合、前2つは404、最後の1つは403でレスポンスが帰ってくるとする。

この場合に、最後の xiqaapes のページだけ副次的な効果で「存在をすること」を表現してしまう。

この副次的な効果で不都合が発生する場合がある、それは悪意を持ったユーザが権限内の情報を探し当てたいときに、情報があることのヒントとなってしまうことである。

些細な情報かもしれないが、少ないヒントを掛け合わせて悪意のあるユーザは攻撃などをしかけるので、「本当に認証している人以外には存在を秘匿したほうがよい」というケースの場合はレスポンスとしても存在しない体裁にして404を返す場合がよいケースもあるので、ページのコンテキストによっては検討したほうが良い。

なお、GitHubなどが404にする方向性を取っており、アクセスする権利があれば正常に表示されるページもログアウトすると404扱いになって見れない形になっている。

参考リンク

AWSにはコンテナやストレージなどの単位のサービス資料を検索できるページがある

小ネタ

紹介サイト

いいところ

「コンテナ」「データベース」など、ある程度ジャンルに分かれて資料がおいてあるので興味のあるものをつまみ食いしたいときに便利

関連リンク

Railsにてbefore_actionでredirect_toしてしまうとafter_actionなどは実行されない

actionに対しての実行なのでそのとおりといえばそのとおりなんですが備忘として。

環境

$ bin/rails --version
Rails 6.0.4.1

TL;DR

  • before_actionでredirect_toをするとafter_actionでは呼ばれない
  • 回避案はいくつかあるが、action内でredirect_toをするなどすれば回避できる

今回考えるケース

以下のように必ず出力してほしいロギングを after_action に設定し、特定の条件下でリダイレクトを行うようなケース

ケースに対してbefore_actionでredirect_toをし、after_actionでロギングする場合

コード

class RedirectTestsController < ApplicationController
  before_action :redirect_check, only: :show
  after_action :after_logging

  def index
    render json: { path: "index" }
  end

  def show
    render json: { id: params[:id] }
  end

  private

  def redirect_check
    if params[:id].to_i == 10
      logger.info("***** redirect *****")
      redirect_to "/redirect_tests/"
    end
  end

  def after_logging
    logger.info("***** must after_logging! time: #{Time.zone.now} *****")
  end

end

route.rbは以下

Rails.application.routes.draw do
  resources :redirect_tests
end

このケースにおける問題点

上記のコードで http://localhost:3000/redirect_tests/1 にアクセスすると以下のようなログが出る。

Started GET "/redirect_tests/1" for ::1 at 2022-04-23 22:46:31 +0900
Processing by RedirectTestsController#show as HTML
  Parameters: {"id"=>"1"}
***** must after_logging! time: 2022-04-23 13:46:31 UTC *****
Completed 200 OK in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 335)

そして http://localhost:3000/redirect_tests/10 にアクセスすると、以下のようなログが出る

Started GET "/redirect_tests/10" for ::1 at 2022-04-23 22:48:05 +0900
Processing by RedirectTestsController#show as HTML
  Parameters: {"id"=>"10"}
***** redirect *****
Redirected to http://localhost:3000/redirect_tests/
Filter chain halted as :redirect_check rendered or redirected
Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 308)


Started GET "/redirect_tests/" for ::1 at 2022-04-23 22:48:05 +0900
Processing by RedirectTestsController#index as HTML
***** must after_logging! time: 2022-04-23 13:48:05 UTC *****
Completed 200 OK in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 317)

上記のようにbefore_actionでredirect処理が入ると、after_actionが実行されることがない。原理としては明確でactionの処理を実行することなく終わるため、actionが終わったあとに呼び出す after_action が実行されない。

もちろんaround_actioのaction実行後の処理も呼び出されない。

改善案

いくつか方法論はありますが、before_actionとafter_actionの使い方をなるべく踏襲する形だと「レンダリング処理をコールバックでやるのを避ける」というのがあるので、今回はそちらを改善案として紹介します。

アプローチとコード

before_actionでリダイレクト処理を挟むとactionに到達する前に違うURLに行ってしまうので、遷移を挟む処理はbefore_actionでさせないようにする

class RedirectTestsController < ApplicationController
  before_action :redirect_check, only: :show
  after_action :after_logging

  def index
    render json: { path: "index" }
  end

  def show
    redirect_to "/redirect_tests/" and return if @redirect_flag
    render json: { id: params[:id] }
  end

  private

  def redirect_check
    @redirect_flag = false
    if params[:id].to_i == 10
      logger.info("***** redirect flag on *****")
      @redirect_flag = true
    end
  end

  def after_logging
    logger.info("***** must after_logging! time: #{Time.zone.now} *****")
  end

end

このやり方で http://localhost:3000/redirect_tests/10 にアクセスすると、以下のようなログが出て、ロギングがリダイレクト時にもちゃんとでる。

Started GET "/redirect_tests/10" for ::1 at 2022-04-23 22:58:14 +0900
   (0.1ms)  SELECT sqlite_version(*)
Processing by RedirectTestsController#show as HTML
  Parameters: {"id"=>"10"}
***** redirect flag on *****
Redirected to http://localhost:3000/redirect_tests/
***** must after_logging! time: 2022-04-23 13:58:14 UTC *****
Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 512)


Started GET "/redirect_tests/" for ::1 at 2022-04-23 22:58:14 +0900
Processing by RedirectTestsController#index as HTML
***** must after_logging! time: 2022-04-23 13:58:14 UTC *****
Completed 200 OK in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 336)

参考リンク

変数名に省略形をつかっていいかの判断基準

自分なりの判断基準を整理したものです。

TL;DR

  • 「略語は必ず使ってはならない」と絶対的に言えるほど否定するものでもないので、チームで使い方を整理するのがベスト
  • その際に考えるべきことは以下
    • 狭い範囲でしか通じない略語は避けるべきだが、広く一般で使われている略語は問題ない
    • ただし略語が全プログラマには通じるとは限らないので気をつける
    • 一般的に使われる略語でも、コンテキストによっては誤読する略語になりうるのでその場合は利用を避ける

リーダブルコード曰く

リーダブルコードには以下のような文章がある。

ぼくたちの経験からすると、プロジェクト固有の省略形はダメだ。新しくプロジェクトに参加した人は、暗号みたいに見えて怖いと思うだろう。しばらくすると、それを書いた人ですら暗号みたいで怖いと思うようになる。 新しいチームメイトはその名前の意味を理解できるだろうか? 理解できるなら問題ない。 プログラマは、evaluationの代わりにevalを使う。documentの代わりにdocを使う。stringの代わりにstrを使う。だから、新しいチームメイトもFormatStr()の意味は理解できる。 - リーダブルコード 2章 名前に情報を埋め込む 頭文字と省略形

このように「プロジェクト固有の略語は避けるべき」ということと「一般的に知れ渡っている略語なら良い」ということが書かれている。

一般的に知れ渡っている略語

リーダブルコードでは「プログラマは、evaluationの代わりにevalを使う。documentの代わりにdocを使う。stringの代わりにstrを使う。だから、新しいチームメイトもFormatStr()の意味は理解できる。」とありその中で「プログラマは」かなり大きめの主語で書かれているが、ここではプログラマがドキュメントを読む上で読み違えのないような略語を指すと思われる。例えばある程度の量のドキュメントやソースコードに接してきたプログラマstrstranger とは誤読しないし、 varvariety と勘違いすることは少ないと思われる。

ただ、「connconnection だ」のような言葉のマッピングは経験値で培われるもので、網羅的な情報があるわけでもなく、コンテキストによっては全く知る機会がないこともあるので、前提として「知らない人がいるかもしれない」という心持ちでコードレビューなどには望んだほうが良いと思われる。

略語と誤読

一般的な略語であれば問題ないかと思われるが、例外として一般的な略語でも誤読が誘発されるものは避けるべきだと考える。

例えばプログラムの処理内容として「バラエティー番組の種類を選択させたい」というような内容を記述するときに varietyvariation という単語が登場する。そのときに一時的な変数名などに var を使うと、コードの作り方によっては「もしかしてバラエティー番組のことを指しているのか?」といらぬ懸念を抱かせてしまうので、このような状況下においては略語を避けて正しい言葉を当て込むほうがよいケースもあると思われる。

絶対的な線引はつくりにくい

前提が「一般的に知れ渡っているものなら略語でも大丈夫」としたいところだが、誤読を誘発するようなものなら避けるべきなので、何も考えない方向にいくなら「略語は原則使わない」だがそれも窮屈すぎる場面があると思われる。

そのため、やはりどの程度で抑えるかはそのコードに関わる人たちの認識次第なので、リーダブルコードの一節を起点に認識合わせをしてチームとしてどうあるべきかの方向性を話をすると良いのだと思います。

関連リンク

多くのブラウザはHTMLのheadにfaviconに関する記述がないと/favicon.icoに取得をしようとする

当たり前の話なんだけど、暗黙の仕様の様子なので結果をまとめる

TL;DR

  • どのブラウザもファビコンのデータが欲しいので明示的にない場合は取得しにいく
  • ブラウザごとにタイミングは異なるが、大体はページのHTMLを取得したあと
  • 最後にアクセスしたページをCookieに保存しようとすると、favicon.icoへのアクセスになる可能性があるので気をつける

favicon自体の挙動

2022年現在ではHTML Living Standardで、faviconの指定は <link rel="icon"> で指定できるようになっている(参考:faviconの仕様はHTML5から標準化されている - コード日進月歩

そのためブラウザに記述があればその指定されているパスのデータを取得する動作をする。

ただし、指定がない場合は / 配下にある favicon.ico を探し、ファイルがあればそれを使用する。これはfaviconの仕様の始祖であるIEがそういう動きだったことに由来すると思われる。(一種のデファクトスタンダード

詳しくは英語版のwikipediaにてまとめられているのでそちらを参照のこと。

faviconを取得するタイミング

前項で紹介したwikipediaにも記載があるが、多くのモダンブラウザでは「タブバー」もしくは「アドレスバー」にfaviconを出す仕様となっているので、ページを表示する場合にはほぼ確実にfaviconを取得する。そのため多くのブラウザでは閲覧するページに<link rel="icon">がない場合、次点として /favicon.ico にアクセスし表示できるデータがないかを探していく

気をつけないといけない「最後にアクセスしたページ」という観点

この場合気をつけないといけないのは「最後にアクセスしたページ」が favicon.ico になりうるということ。

昨今のWebアプリケーションは画像のアセットが別のドメインにあったりすることが多いので大体ページのgetリクエスト以降はアプリケーションサーバにアクセスすることが少なくなったので「最後にgetのあったリクエストを最後に閲覧したページとしてみなす」としても問題なく挙動してしまうシーンはあるが、このfavicon.icoが未指定の場合はfavicon.icoが最終アクセスページとして認識されてしまう可能性がある。

そもそも論Webブラウザの挙動を認識していれば最後にアクセスしたページ = 最後にgetリクエストがあったページという関連付けが成立しないことはわかるが、認識違いを起こすこともあるので気をつけたほうがいい。

関連リンク

Rails6でpublic配下を見れるようにする設定値はconfig.public_file_server.enabled

developmentでは見れたのに、productionじゃ見れないのはなぜ…となるときに見直す設定

環境

$ bin/rails -v
Rails 6.0.4.1

想定している状況

/public配下のファイルが、RAILS_ENVがdevelopmentだと見れて、productionのときに見れないときに考える設定

Railsガイド曰く

public/ディレクトリ内の静的アセットを配信するかどうかを指定します。デフォルトではtrueが設定されますが、production環境ではアプリケーションを実行するNginxやApacheなどのサーバーが静的アセットを扱う必要があるので、falseに設定されます。 - Rails アプリケーションを設定する - Railsガイド - 3.1.35 config.public_file_server.enabled

ソースコード上も初期値はtrue

どこで設定するのか

デフォルトのRailsであれば config/environmentsRAILS_ENV別の設定

productionに関しては以下のようになっている

  # Disable serving static files from the `/public` folder by default since
  # Apache or NGINX already handles this.
  config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?

関連リンク

GoogleChromeではhttpsやwwwを表示しないのは何故かをざっくり調べる

なぜ消えるのか、という話。

情報元

https://bugs.chromium.org/p/chromium/issues/detail?id=883038#c114

バグトラッキングに記載されていること

原文を転載すると以下

The Chrome team values the simplicity, usability, and security of UI surfaces. To make URLs easier to read and understand, and to remove distractions from the registrable domain, we will hide URL components that are irrelevant to most Chrome users. We plan to hide “httpsscheme and special-case subdomain “www” in Chrome omnibox on desktop and Android in M76.

Google翻訳を駆使して和訳すると以下

Chromeチームは、UI surfacesのシンプルさ、使いやすさ、セキュリティを高く評価しています。 URLを読みやすく理解しやすくし、登録可能なドメインから気を散らすものを取り除くために、ほとんどのChromeユーザーに関係のないURLコンポーネントを非表示にします。 デスクトップのChromeオムニボックスとM76のAndroidで「https」スキームと特殊なサブドメイン「www」を非表示にする予定です。

ということで、「https」などのURIスキーム部と「www」というサブドメインはユーザのために不要な情報なため消したという判断。

関連リンク