業務でログイン機能を持ったWebサービスを扱っているが、
- 実際どのような認証方法が存在するのか
- それぞれがどのような仕組みで認証を行っているのか
という部分への理解が浅いと感じた為、それらを調べてまとめる。
認証にはどのような方法が存在するのか
今回は下記の認証方式の方法と考慮する点などを確認していく。
- Basic Authentication
- Digest Authentication
- Session based Authentication
- Token based Authentication
確認環境
各認証方式を実装したdockerコンテナ(Go 1.19
& gin
)へ nginx
がproxyでリクエストを振り分ける、という構成をローカル環境で作成して確認。
Basic Authentication
ベーシック認証は、ブラウザのプロンプトにユーザー名とパスワードを入力することで認証する認証方式。
認証の流れ
- クライアントが未認証で認証が必要なURLへリクエストを送る。
- サーバーは、
Basic
という値を持ったWWW-Authenticate
をレスポンスheaderに設定して、Status Code401 Unauthorized
でレスポンスを返す。 - クライアント側では、ユーザー名とパスワードを入力するプロンプトが表示される。
- プロンプトにユーザー名とパスワードを入力することで、
user:pass
という形式の文字列をbase64
エンコードした文字列を、リクエストheaderのAuthorization: Basic
に設定して、再度1
でリクエストしたURLへリクエストを送信する。
利点
- 実装が簡単
考慮・懸念事項
- ユーザー名とパスワードがbase64エンコードされるだけで暗号化されていない。
- 認証時にプロンプトへの入力が必要になる。
Digest Authentication
ダイジェスト認証は、前述のベーシック認証と認証の流れは似ているが、パスワードをサーバーに送信する際に MD5
という形式でパスワードがハッシュ化される為、セキュリティ面で優れている。
認証の流れ
- クライアントが未認証で認証が必要なURLへリクエストを送る。
- サーバーは、
nonce
というランダム値を生成して、nonce
とDigest
という値を持ったWWW-Authenticate
をレスポンスheaderに設定して、Status Code401 Unauthorized
でレスポンスを返す。 - クライアント側では、ユーザー名とパスワードを入力するプロンプトが表示される。
- プロンプトにユーザー名とパスワードを入力することで、ハッシュ化されたパスワードとレスポンスに含まれていた
nonce
を、リクエストheaderのAuthorization
headerに設定して、再度1
でリクエストしたURLへリクエストを送信する。
利点
- 実装が簡単
- MD5でハッシュ化されている為、ベーシック認証よりもユーザーの認証データの扱いがセキュア
考慮・懸念事項
- 認証時にプロンプトへの入力が必要になる。
- ハッシュ化したパスワードをサーバーで確認する都合から、パスワードがサーバー内で平文管理されることになる。
Session based Authentication
セッションベース認証は、session
と cookie
を利用することで、リクエストの度に認証情報を送受信する必要を無くしている。
セッションストアとして、メモリを使用することも可能だが、サービスのスケールを考慮する場合は、RDBやRedis, Memcachedなどのキャッシュサーバーを利用するのと良い。
認証の流れ
- ユーザーが認証情報をサーバーに送信する。
- サーバーで正しく認証が完了した際に、サーバーでセッションデータを生成してセッションストアに保存。
- 保存したセッションデータに紐づく
sessionID
を レスポンスのcookie
に設定する。 - レスポンスを受け取ったブラウザは、
sessionID
をcookie
として保存して、それ以降のそのサーバーへのリクエスト時には毎回そのcookie
がリクエスト内容に付与される。
利点
- ベーシック認証やダイジェスト認証のように、リクエスト毎にユーザーの認証情報を送受信する必要がない為、認証完了後の処理の効率が良い
- 多くのFWで機能が存在する為、実装がしやすい。
考慮・懸念事項
- サーバーでユーザーの認証情報を保管する必要がある。(ステートフル)
各脆弱性と対応方法
cookie
を使用する場合、下記の脆弱性の可能性がある為、それぞれ対応・検討が必要。
セッションハイジャック
- cookieに
Secure
属性を設定する- secure属性を設定したcookieはHTTPS通信でのみ利用される為
XSS
- cookieに
HttpOnly
属性を設定するdocument.cookie
などJS経由でcookieに設定したsessionIDを取得される可能性がある為
CSRF
- cookieに
SameSite
属性のStrict
orLax
を設定する- SameSite属性として、StrictかLaxを設定することでcookieの送信を制限することができる。
Origin
リクエストヘッダーを確認する。- Originリクエストヘッダーとサーバーが稼働しているホストが同じかを確認することで、想定していないドメインからのリクエストを検知することができる。
- formの中に動的に
<input type="hidden" value="token-xxx">
を設置する。- sessionIDとは別にhiddenタイプで設定されたトークンを確認することができる。
- CSRFトークンを生成して cookieに
XSRF-TOKEN
として設定する- axiosなどのライブラリで取得処理などを行う際に、cookieに設定されたXSRF-TOKENの内容を送信することで、sessionIDとは別にサーバー側でトークンの確認を行うことができる。
Token based Authentication
トークンベース認証は、cookieの代わりにトークンを使用する認証方法で、一般的に JSON Web Token (JWT)
のフォーマットで実装されることが多い。
JWTとは
JWTは、JSON形式のデータを署名付きでやりとりすることができるトークン形式。
下記の Header
, Payload
, Signature
の3つから構成されている。
Header
Headerは、下記の2つで構成され、Base64Url
でエンコードされる。
typ
:トークンタイプ(JWT)alg
:使用される署名アルゴリズム(HMAC SHA256
,RSA
、、など)
{
"alg": "HS256",
"typ": "JWT"
}
Payload
Payloadは、ユーザーのデータなどを扱う claims
(クレーム)で構成されている。
claimsは、RFCで定義されているものもあるが、ユーザーが付与したいデータを設定することができる。
下記のようなJSO形式をBase64Url
でエンコードする。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature
Signatureは、Base64Url
でエンコードされたHeaderとPayload, secretを基に生成され、メッセージが途中で変更されていないか確認する為に使用される。
(JWTが秘密鍵で署名されている場合、送信者が本人であることの確認も行うことができる)
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
トークンの生成
トークンは、前述のBase64Url
でエンコードされたHeader
, Payload
, Signature
を.
(ドット)で結合した文字列になる。
トークンの送受信
調べているかんじ、トークンの送受信は下記のような Authorization header
or cookie
で行われている。
(Authorization headerでの送受信の場合は、cookieを使用しない為、CORSの問題が発生しない。)
Authorization: Bearer <token>
認証の流れ
- ユーザーが認証情報をサーバーに送信する。(
Payload
の項目内容) - 認証完了後、サーバーでトークンを生成してユーザーにレスポンスを返す。
- ユーザーは受け取ったトークンをリクエストheaderやCookieに設定して、目的のエンドポイントにリクエストを送信する。
- リクエストを受け取ったサーバーで、設定されているトークンの署名が正しいかを確認する。
利点
- サーバーはユーザーから送信されるトークンの署名が正しいかの確認をするだけで良い為、セッションベース認証のようにサーバーに認証情報を保管する必要がない。(ステートレス)
- ただ、考慮事項に記載している生成したトークンを削除できないことから、JWT内にセッションIDを含めて、JWTを改善チェックの手段として使うハイブリッドな使用方法が良いっぽい。
- セッションベース認証同様に、各言語でJWT用のライブラリが存在する為、実装がしやすい。
考慮・懸念事項
- 生成したトークンを削除する方法は無い(サーバー側で
jti
(JWT ID) Claimを管理するなど間接的な方法しかない)
localstorageでのJWTの保存方法
生成したJWTをブラウザ側のlocalstorageで保存する場合、XSS脆弱性の問題がある為、利用は避けるべき。
JWTでのセッション管理
生成したトークンを削除できない問題 & 脆弱性の観点から、長期間でのセッション管理用途としてのJWT利用は避けた方が良さそう。
(仮に生成したトークンが漏洩&悪用された場合、JWTで使用している秘密鍵などを再度設定するなどしないとトークンを無効することができない)
まとめ
- 1度だけの認証であれば短い有効期限を設定したJWTでの認証が良さそう。
- セッション管理を行いたい場合は、JWTで無理矢理対応するのではなく、従来通りサーバー側でセッション管理を行い、改竄などへの対策としてSessionID in JWTというような方法を検討する。という方が良さそう。
※ 下記2つを後日調べて追記する
- OAuth - Open Authorization
- SSO - Single Sign On
参照
- Web Authentication Methods Compared
- JSON Web Tokens vs. Session Cookies: In Practice
- HTTP Cookie の使用 - HTTP | MDN
- SameSite cookies - HTTP | MDN
- Origin - HTTP | MDN
- CSRFトークンがCookieに存在するという勘違い
- 今時の CSRF 対策ってなにをすればいいの?
- Introduction to JSON Web Tokens
- JWTでセッション管理してはいけない - Qiita
- JWTは使うべきではない 〜 SPAにおける本当にセキュアな認証方式 〜 - Qiita
- "JWT=ステートレス"から一歩踏み出すための考え方