ユーザログイン（ユーザ認証）の歴史

#はじめに
Webアプリに限らず、ほとんどのサービスではユーザ名とパスワードを入力し、ログインをしてから
サービスを使う
OSのログインしかり、メールアプリ、Webサービスなど
ユーザ名とパスワードの入力という基本部分はほぼ変わってないが
時代とともにより安全な仕組みへと進化しているので
私の知る限りの説明したいとおもう

#認証、認可の違い
まずややこしいのが、認証、認可（承認）のちがいである
認証(Authentication) は、ユーザーが正しいかどうかの確認（パスワード認証）で
認可、承認（Authorization）は、アクセス権の確認である

https://garafu.blogspot.com/2013/11/authentication-authorization.html

昔はこの認証、認可を同時に行っていたが、OAuth等の登場から厳密に分ける必要が出てきた
ただ、いまだにHTTPヘッダでも混同して使われていたりする

#歴史
ただし順番に並んでいるともかぎらないし、抜けもある

##平文ベーシック認証
いわゆるBasic認証と称して、ユーザ名とパスワードが平文で送られていた
今考えると恐ろしい世界だ
悪意ある人が一度その情報を取得すると、簡単に乗っ取ることが可能
また、パスワードは他のサイトでも使い回す事が多いので、下手すると全てのサービスが乗っ取られる

初期の頃は、フォームからPOSTなどで平文パスワードをログイン時に送っていた
（なかにはGETでクエリパラメータでURLにパスワードが平文でのってる恐ろしいサービスもあった）

Postで送らない方法もある。たとえばHTTPヘッダに乗せるなど
また、TextではなくJson等で送る方法もある
が、平文であればいずれも問題である

##ハッシュ
平文でパスワードを送ると盗まれると危険である
そのためパスワードをHashし、それを送ることで平文よりは安全になる
Hashとは、不可逆な一方向変換である
https://qiita.com/chroju/items/3ddae568206b8bc3d8f9

これで、パスワード自体が取られる事はなくなった
passwordより5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8 の方がわかりにくいでしょ？

sha1やmd5のような暗号強度の低いものはレインボーテーブルという、Hashの逆引きデータが販売されている
レインボーテーブル
https://dev.classmethod.jp/security/rainbowtable/

ので、より強度な暗号アルゴリズムを使い、さらに ソルトやストレッチングを行い
暗号強度を増やすべきである

##ランダムソルト＆ストレッチング
パスワードをハッシュ化したので、パスワードを知られる事はなくなった（解読されなければ）
しかし、ハッシュ値を知っていればなりすましログインも可能だし、同じハッシュのユーザは同じパスワードだとわかる
ゆえに、ユーザーごとに異なるSaltをつければ上記の問題がなくなり、安全性もあがる
Saltとはパスワードに追加する追加文字列である
また、Saltと同時にストレッチング（Hashの重ね掛け）を行うと強度が増す
上記処理には PHPの `password_hash` 関数などが有名

##セッション＆クッキー
通信のたびにユーザ名とパスワードを送るのはハッキングを受ける確率も増えるし
毎回認証のオーバーヘッドは避けたい
そこでセッションを使う
はじめにサーバはクライアントにランダムな値のセッションIDを送る。
サーバはセッションIDと紐付ける事でクライアントの状態を保持できるため
認証が成功すれば、その後はセッションIDの確認だけで認証が不要になる
セッションIDは盗まれても、ワンタイムなのでそこまで痛くない。一定期間で無効になるし、パスワード情報が何もない
また、セッションには認証だけでなく、それまでのやり取りのステート（例えばショッピングカートにアイテムを入れる）も保存することが可能
https://qiita.com/7968/items/ce03feb17c8eaa6e4672

###セッションハイジャック
URLパラメータにセッションIDを入れると簡単にセッションを乗っ取られる
セッションIDは Cookieに保存することで、簡単かつ比較的安全にセッション管理が出来る
（もちろんCookieを盗んだりも可能であるが、URLパラメータに表示するよりは圧倒的にましである）

###セッション・フィクセーション
攻撃者が有効なセッションIDを対象のCookieに送り込んで、以後そのセッションIDが対象とひも付き
攻撃者が自由に乗っ取れる
これは、セッションIDがユーザ固有ではなく、セッションIDがランダムでかわらないサイトだとおこりうるが
ユーザごとにセッションIDを発行していればおきない

###CSRF（Cross Site Request Forgery）
https://qiita.com/maruloop/items/e14d02299bd136f4b1fc
ログインとは少し話がずれるが、気をつける必要がある
何かしらの方法で悪意のあるURLやJavaScriptを踏ませる
具体的にはimageタグやメール等をクリックさせる
そうすると、偶然裏でサービスにログインしていた場合、悪意のあるURLが正規呼び出しだと勘違いされ
実行されてしまう
例えばブログへの書き込みだったり、銀行振り込みかもしれない
単純な攻撃ではあるが、防ぐには少し厄介だ
Referで判定できればいいのだが、そうもいかない
そこで最も有効だと思われるものに
Hiddenにワンタイムトークンを仕込む事である
トークン（CSRFトークンと呼ぶ）を識別することで、認証を行ったページからのアクセスを判断可能

c-yanさんの指摘で、Hiddenではなくヘッダに入れる実装もあるとのことですが、そのあたり他の要素もからみ纏められなかったのでリンクにて。
https://www.scutum.jp/information/waf_tech_blog/2017/11/waf-blog-051.html

##Authorizationヘッダ
Webページの場合、セッションをCookieに入れると良いとかいたが
WebAPIではCookieが使えないため、Cookieに依存すべきではない
その場合はAuthorizationヘッダを使うべきだ

https://qiita.com/hirohero/items/d74bc04e16e6d05d2a4a

間違ってもURLにクエリパラメータとして置いてはいけない。
下記のHTTPSで保護しても無駄になるし

##HTTPS
TLSレイヤーにて暗号化を行っている
設定によっては脆弱性があるが非常に強力で
基本的にはHTTPSを使えばBasic認証で送ってもかなり安全ではある

https://qiita.com/kuni-nakaji/items/5118b23bf2ea44fed96e

ただし、古いバージョンは脆弱性があるので注意

下記のまとめは個人的に有用だと思った
https://qiita.com/mpyw/items/bb8305ba196f5105be15

##OAuth、OpenID
色々あり今使われているのは
OAuth2とOpenID Connect

https://www.buildinsider.net/enterprise/openid/connect

簡単にいうと、OpenID Connectは、OAuth2 + User情報取得 である。
だが実際はFacebook等のOAuth2のサーバでもUser情報は取得している（/meへリダイレクト）
やはり認証と認可をセットにすることが多い。
つまり、OpenID Connectだと、取得ページへのリダイレクトがないぶん効率良く認証が出来る

どちらも肝の部分は
認証サーバ（たとえばFacebookやTwitter）を使い、認証サーバにログインし、権限のトークンをもらう（認可）
そのトークンの権限を使い、認証サーバの操作が可能になる。
たとえばFacebookのウォールの読み書きをしたいなら、読み書き許可のあるトークンを使いFacebookにアクセスする
ここで素晴らしいのは、ログイン（認証）はFacebookにFacebookで行う事ができるため
自サーバにFacebookのログイン情報を保持しなくてよい

##JWT
Json Web Token
OpenIDのトークンで使われる
https://qiita.com/TakahikoKawasaki/items/8f0e422c7edd2d220e06
OAuth2でも使われていると思ったが、アクセストークンは定義されてないっぽい？？

https://webbibouroku.com/Blog/Article/jwt

JWSで出来る事は、改ざん防止だ（暗号化はない）
仕組み的には、ヘッダ、ペイロード、署名から成り立つ
署名にはメッセージのハッシュ値が入っているので、改ざんすることは不可能だ
ただし、暗号化されていないので、だれでも内容を見ることはできる

一般的にはJWSのペイロードに ユーザIDとトークンの有効期間を置く
JWSは署名により改ざん不可能なため、このユーザしか使えない、期間限定トークンだ
セッションIDに近いが少し違う
セッションIDはランダムな値なため、サーバ上のDBと照らし合わせて判定が必要だ
Webサーバがスケールした時、異なるサーバへ割り当てられた場合セッションの照合が出来ない
（そのため、複数のWebサーバで運用するには DBなりKVSでのセッション共有必要）
ところがJWSの場合は、サーバの秘密鍵で署名の有効性を確認できるので
KVSもDBも不要です（DBについては後述・・）

JWEは暗号化も含めた規格だが、あまりドキュメントがないし
HTTPSがあれば大丈夫（とか思ってはいかんのだろうか？）


###JWTの危険性
前に仕事で、JWTは危険だという記事を見せられこれについてどう思うか？と言われた

https://postd.cc/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid/

JOSEというのは、JWTの中の署名(JWS)と暗号化(JWE)の総称である
一般的にJWTといえばJWSの事だと思っていい
JOSEの脆弱性は、暗号化方式を noneやHMAC（共有鍵方式）
に書き換えて改ざんが出来ることだ
大抵のフレームワークでは既に対応されてると思いたいが
暗号化方式の noneとHMACは弾くようにすべきだ
（これらがアルゴリズムと設定出来る事が脆弱性だ）
なので、上記を気をつければ、積極的に使うべきだと思う

JWTはBase64urlエンコードされているためURLセーフだ
OAuth等でよく使われる bearerトークンは Token68を満たす必要があるが
Base64(url)はToken68要件を満たすため、JWTトークンはそのままbearerトークンとして送る事ができる
HTTPヘッダに
Authorization: bearer <token>
と送ってしまおう

##JWTのsalt
上記、JWSを使えばセッションIDのようにDBを参照する必要がない
と言ったが、ちょっとウソでした
改ざん防止してあるので、DBなしで認証出来るが
同じトークンを使い回す事が可能である
（有効期間をつければ、一定時間で無効にはなるが）
よりセキュアに、トークンを1回で使えなくするために
ペイロードにユーザ事、通信のたびに変動する乱数のSaltを入れるべきである
結果として、SaltをユーザごとにDB管理する必要がある


#まとめ
HTTPSを使い
パスワードは十分にセキュアなHash方式を採用し
ユーザーごとに異なるSalt付与やストレッチングを行なう
DBにはHashされたパスワードを保存し、ログイン時に認証を行なう
その後の通信は、SaltつきのJWT等でワンタイムトークンにて認証する
HTTPSで保護されてるが、通信内容を暗号化したい場合はJWEなり別途暗号化を入れる

ぐらいしておけば、普通の用途には十分と思われる
暗号化とデータ改ざんは、基本的には強固になればCPU負荷が増えるため
セキュリティ要件とのトレードオフになる事が多い