最終確認日:2026年8月13日
本記事はMandiant M-Trends 2026、Cloudflare 2026 Threat Report、NIST SP 800-63B-4、MITRE ATT&CK、RFC 9700、Django 5.2、django-allauth公式資料を確認して作成しています。
冒頭文

これまでWebサイトのアカウント防御では、
Passwordを守る
↓
MFAを追加する
↓
不正Loginを防ぐ
ことが中心でした。
もちろん、現在もPasswordやMFAは重要です。
しかし2026年に注意したいのは、攻撃者が**「Loginするための情報」ではなく「すでにLogin済みであることを証明する情報」**を狙う攻撃です。
MandiantのM-Trends 2026では、攻撃者が長寿命のOAuth TokenやSession Cookieを取得し、MFAを改めて通過せずSaaS Sessionを乗っ取る問題が重要テーマとして扱われています。第三者SaaSからIntegration TokenやPersonal Access Tokenを盗み、別環境へ横展開するケースも指摘されています。(M-Trends 2026)
Cloudflareの2026 Threat Reportも、LummaC2などのInformation Stealerが有効なSession Tokenを窃取し、従来型MFAを通らず認証後の操作へ進む攻撃を取り上げています。(Cloudflare 2026 Threat Report)
つまり、現在は次の両方を守る必要があります。
【認証前】
Password
Login by Code
Passkey
TOTP
WebAuthn
↓
本人を確認する
【認証後】
Session Cookie
OAuth Access Token
Refresh Token
↓
本人確認済み状態を維持する
本記事では、以前の2記事、
の続きを意識して、
Loginに成功した後、その「ログイン済み状態」をどう安全に維持するか
を解説します。
結論:強い認証だけでは足りない。「Sessionを認証情報として守る」必要がある
結論から言えば、
Passkey、TOTP、WebAuthn、Login by Codeを導入しても、発行後のSession Cookieが盗まれれば安全とは言えません。
MITRE ATT&CKは、Session Cookieを盗み、Passwordを知らなくても認証済み利用者としてWebサービスへアクセスする攻撃をT1539 Steal Web Session Cookieとして定義しています。Session Cookieによって一部MFAを迂回できる場合があることも説明しています。
安全なWeb認証は、
強いLogin
+
安全なSession発行
+
Sessionの期限管理
+
重要操作時の再認証
+
異常Sessionの検知
+
Sessionを利用者自身が失効できる
まで設計して初めて成立します。
この記事の要点
2026年はPasswordだけでなくSession CookieやOAuth Tokenの窃取が重要な攻撃テーマになっている。
Session Cookieを盗まれるとMFAを再度要求されず認証済みSessionを悪用される場合がある。
PasskeyやWebAuthnはLoginを強化するが、発行済みBearer Sessionの窃取を自動的に防ぐものではない。
DjangoのSession CookieはPasswordに近い重要情報として扱う必要がある。
Session CookieにはSecure、HttpOnly、SameSiteを適切に設定する必要がある。
Session IDやRefresh TokenをlocalStorageへ保存する設計は避けるべきである。
ログイン状態を安全に長く維持するには「長寿命Session」ではなく「通常Session+再認証」を組み合わせる。
メール変更、Password変更、MFA変更などはログイン済みでも再認証を要求するべきである。
django-allauthには再認証とUser Sessions管理の仕組みがある。
Social Loginだけが目的ならOAuth Access Tokenを不要にDB保存しない設計が望ましい。
OAuth Refresh TokenにはRotationやSender Constraintを利用できる場合がある。
Logout、Password変更、MFA変更、端末紛失時にSessionを失効できる設計が必要である。
IPアドレスだけでSessionを強制固定すると正規利用者を誤検知するため、IPはRisk Signalとして扱う方がよい。
この記事で分かること
この記事では次の順番で説明します。
なぜ「ログイン済み」が狙われるのか
Session Cookieとは何か
OAuth Tokenとの違い
Login by CodeとSession保護の関係
Passkey・TOTP・WebAuthnとSession保護の関係
Session Tokenが盗まれる経路
自分のDjangoサイトを確認する方法
Session Cookieを安全に設定する方法
ログイン状態を安全に維持する方法
django-allauthで再認証を入れる方法
User Sessionsを利用者へ見せる方法
OAuth Tokenをどう管理するか
異常Sessionをどう検知するか
Session Token流出時の対処
再発防止
対象読者・前提環境
主な対象は次の方です。
Django開発者
django-allauth利用者
Webサービス運営者
SaaS開発者
インフラエンジニア
SOC / CSIRT担当者
Identity Security担当者
コード例はDjangoの一般的なSession認証を対象にします。
設定名はDjango 5.2および現在のdjango-allauth公式資料を基準に確認しています。利用中のバージョンによって挙動が異なる可能性があるため、本番反映前には必ず対象Versionの公式Documentationも確認してください。
まず理解したい:AuthenticationとSessionは別物
初心者向けに、テーマパークで例えます。
Authenticationは「入口の本人確認」
入口で、
チケット
+
本人確認
を行います。
Webサービスなら、
Password
Passkey
TOTP
Login Code
WebAuthn
などです。
Sessionは「入場後につけるリストバンド」
毎回アトラクションへ乗るたび、
身分証明書を見せる
Passwordを入力する
MFAする
のは大変です。
そこで入口で本人確認に成功したあと、
この人は確認済み
と分かるリストバンドを渡します。
Webでこの役割を果たす代表例が、
Session Cookie
です。
NIST SP 800-63B-4も、Authentication後に毎回Credentialsを提示する代わりにSessionを開始し、Session Secretによって認証済み状態を継続する仕組みを定義しています。
Session Cookieを盗まれるとは「入場済みリストバンドを盗まれる」こと
攻撃者がPasswordを盗んだ場合、
Password
↓
Login
↓
MFA
↓
成功
という関門があります。
しかしSession Cookieが盗まれた場合は、
盗んだSession Cookie
↓
「すでに認証済み」と判断される
↓
Applicationへアクセス
となる可能性があります。
MITRE ATT&CK T1539は、この攻撃をSteal Web Session Cookieとして整理しています。
従来型と現在型の違い
【従来型】
Password窃取
↓
Login
↓
MFA
↓
Account侵入
現在は、
【Session Hijacking】
Session Cookie窃取
↓
認証済みSessionを再利用
↓
MFAを改めて要求されない
↓
Account操作
という経路も重要です。
M-Trends 2026とCloudflare 2026 Threat Reportの双方が、Password以外のTokenやSessionを狙う攻撃の重要性を指摘しています。
Session CookieとOAuth Tokenは同じではない
ここは混同しやすい部分です。
| 種類 | 主な目的 | 例 |
|---|---|---|
| Session Cookie | Web Applicationでログイン状態を維持 | Django sessionid |
| OAuth Access Token | APIへ権限を持ってアクセス | Google / Microsoft API Token |
| Refresh Token | 新しいAccess Tokenを取得 | OAuth Refresh Token |
| API Token | API認証 | GitHub PATなど |
| Passkey | 本人Authentication | WebAuthn Credential |
| TOTP | MFA | 6桁Codeなど |
NISTもAccess Tokenについて、単にAccess Tokenが存在することを利用者本人が現在Sessionにいる証明として扱ってはいけないとしています。またAccess TokenやRefresh Tokenは、元のAuthentication Session終了後にも有効な場合があります。
MITREではApplication Access Tokenの窃取をT1528 Steal Application Access Tokenとして別Techniqueに分類しています。
既存記事との関係:Day 08は「入口」、本記事は「入場後」を守る
関連する記事はこちらです。
Day 08:django-allauth Login by Code
https://kurutann.com/diary/detail/django-cms-day-08-allauth-login-by-code/
django-allauthにはMagic Code Loginがあります。
現在の公式Documentationでは、
ACCOUNT_LOGIN_BY_CODE_ENABLED = False
がDefaultです。
有効化すると、Emailへ一度限りのCodeを送信してLoginできます。
現在の公式設定では、
最大試行回数:3
有効期限:180秒
がDefaultです。
これは入口のAuthenticationをどう行うかという問題です。
Loginに成功すると、その後は通常Sessionへ移ります。
したがって、
Login by Code
↓
Authentication成功
↓
Session Cookie発行
↓
以後Sessionで認証状態維持
となります。
Login Codeを強くしても、その後のSession Cookieが盗まれれば別問題です。
Day 09:Passkey・TOTP・WebAuthnも「Session発行前」を強くする
関連記事はこちらです。
https://kurutann.com/diary/detail/django-cms-day-09-passkey-totp-webauthn/
django-allauthは現在、
TOTP
WebAuthn
Recovery Codes
などのMFA機能を提供しています。
WebAuthnはDefaultでは無効で、
MFA_SUPPORTED_TYPES = [
"totp",
"webauthn",
"recovery_codes",
]
MFA_PASSKEY_LOGIN_ENABLED = True
のように有効化できます。
PasskeyやWebAuthnはAuthenticationを強くします。
しかし流れを見ると、
Passkey
↓
本人確認成功
↓
Django Session発行
↓
Session Cookieで継続アクセス
です。
したがって、
Passkeyを入れたからSession Theftまで解決した
とは考えない方が安全です。
3つの記事をつなぐとAuthentication設計が完成する
整理すると次のようになります。
┌────────────────────────────┐
│ Day 08 │
│ Login by Code │
│ │
│ 「どうLoginさせるか」 │
└──────────────┬─────────────┘
│
▼
┌────────────────────────────┐
│ Day 09 │
│ Passkey / TOTP / WebAuthn │
│ │
│ 「本人確認をどう強くするか」│
└──────────────┬─────────────┘
│
▼
┌────────────────────────────┐
│ 本記事 │
│ Session Security │
│ │
│ 「Login後をどう守るか」 │
└────────────────────────────┘
この3層を分けて考えることが重要です。
Session Tokenはどこから盗まれるのか
代表的な経路があります。
Information Stealer
Cloudflareは2026 Threat Reportで、LummaC2のようなInformation StealerがActive Session Tokenを取得し、MFA後の操作へ進むケースを挙げています。
MITREのDetection Strategyでも、BrowserのCookie DatabaseやBrowser Process Memoryへの不審なAccessをSession Cookie Theftの検知対象として挙げています。
Adversary-in-the-Middle型Phishing
偽のLogin Siteが、
User
↓
偽Proxy
↓
本物のWeb Service
の間に入り、
Password
+
MFA
+
発行されたSession Cookie
まで中継する手法があります。
MITRE T1539でもMalicious ProxyによるSession Cookie取得が説明されています。
XSS
ApplicationにXSSが存在すると、JavaScriptから読めるTokenが盗まれる可能性があります。
特に、
localStorage.setItem("access_token", token);
のような設計には注意が必要です。
OWASPはSession ID、Authentication Token、JWT、Refresh TokenなどをlocalStorageやsessionStorageへ保存しないよう勧告しています。JavaScriptから読み取れるためです。
HttpOnlyならXSS対策は完璧なのか
いいえ。
HttpOnlyを付けると、
document.cookie
からSession Cookieそのものを読むことを防ぎやすくなります。
しかしXSSが成立しているBrowserでは、Browser自身に認証済みRequestを送らせることは依然として可能です。
OWASPも、HttpOnlyはCookieの機密性を守る対策であり、XSSそのものを無効化する仕組みではないと説明しています。
したがって、
HttpOnly
+
XSS Prevention
+
CSP
+
CSRF Protection
の多層防御が必要です。
自分のDjangoサイトに関係するか
次の項目を確認してください。
| 確認項目 | 注意度 |
|---|---|
| Login後にDjango Sessionを使用 | 通常 |
| Sessionを長期間維持 | 高 |
| 管理画面も同じSession | 高 |
| MFA変更に再認証なし | 高 |
| Email変更に再認証なし | 高 |
| Session一覧・Remote Logoutなし | 高 |
| TokenをlocalStorage保存 | 非常に高 |
| OAuth Refresh Tokenを長期間保存 | 高 |
SOCIALACCOUNT_STORE_TOKENS=True | 要確認 |
| Session CookieにSecureなし | 非常に高 |
| HttpOnlyなし | 高 |
SameSite=Noneを無条件使用 | 要確認 |
| Cookie Domainを全Subdomainへ拡張 | 要確認 |
| Signed Cookie Session Backendを使用 | 失効要件によって高 |
確認方法1:現在のDjango Session設定を確認する
目的
現在のSession Cookie関連設定を確認します。
実行場所
Django Project Root。
manage.pyが存在するDirectoryで実行します。
コマンド
python manage.py shell -c "from django.conf import settings; print('ENGINE=', settings.SESSION_ENGINE); print('AGE=', settings.SESSION_COOKIE_AGE); print('SECURE=', settings.SESSION_COOKIE_SECURE); print('HTTPONLY=', settings.SESSION_COOKIE_HTTPONLY); print('SAMESITE=', settings.SESSION_COOKIE_SAMESITE); print('DOMAIN=', settings.SESSION_COOKIE_DOMAIN)"
望ましい例
ENGINE= django.contrib.sessions.backends.db
AGE= <設計した値>
SECURE= True
HTTPONLY= True
SAMESITE= Lax
DOMAIN= None
要確認例
SECURE= False
HTTPONLY= False
SAMESITE= None
DOMAIN= .example.com
Noneという表示だけでは危険とは限りません。
設定の意図を確認してください。
判断方法
最低限、
SESSION_COOKIE_SECURE=True
SESSION_COOKIE_HTTPONLY=True
をProductionでは確認します。
Django公式Security DocumentationもHTTPS環境ではSESSION_COOKIE_SECUREとCSRF_COOKIE_SECUREをTrueへ設定するよう推奨しています。
確認方法2:DjangoのSecurity Checkを実行する
目的
Production向けSecurity Settingの見落としを確認します。
実行場所
Django Project Root。
コマンド
python manage.py check --deploy
正常例
重大なWarningが出ない。
異常例
security.W010
などが表示される。
DjangoのSystem Checkでは、Sessionを使っているにもかかわらずSESSION_COOKIE_SECURE=Trueになっていない場合などをSecurity Warningとして検出できます。
判断方法
check --deployが成功しただけで安全とは判断しません。
Application独自のSession設計、OAuth Token保存、再認証などは別途確認します。
対処方法1:Session Cookieを安全な属性にする
Productionの基本例です。
# settings.py
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_DOMAIN = None
SESSION_COOKIE_PATH = "/"
CSRF_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = True
Django 5.2ではSESSION_COOKIE_SAMESITEのDefaultは"Lax"です。Django公式DocumentationはSecure CookieとHTTPS利用を推奨しています。
Secureは何を守るのか
Secure=True
とすると、BrowserはSession CookieをHTTPS通信でのみ送信します。
HTTPへSession Cookieが流れるRiskを減らします。
HttpOnlyは何を守るのか
HttpOnly=True
にするとClient-side JavaScriptからSession Cookieを読み取れないようにします。
NISTもSession Maintenance用Cookieについて、JavaScriptからアクセスできないHttpOnly属性を推奨しています。
SameSiteは何を守るのか
SESSION_COOKIE_SAMESITE = "Lax"
はCross-site RequestでCookieを送信する範囲を制限します。
CSRFを軽減するDefense-in-Depthとして利用できます。DjangoとOWASPの双方がLaxまたはStrictの利用について説明しています。
ただし、
SameSiteはDjangoのCSRF Protectionを置き換えるものではありません。
Django公式DocumentationもSameSiteを追加防御として扱っています。
__Host- Prefixも検討できる
NISTとOWASPはSession Cookieについて__Host- Prefixを推奨しています。
Djangoでは例えば、
SESSION_COOKIE_NAME = "__Host-sessionid"
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_DOMAIN = None
SESSION_COOKIE_PATH = "/"
という設計が可能です。
__Host- Cookieでは、
Secure
Domainなし
Path=/
という制約をBrowser側でも要求できます。
ただし複数ApplicationでCookie NameやPathを使い分けている環境では、導入前に影響確認が必要です。
Reverse Proxy環境ではSECURE_PROXY_SSL_HEADERに注意する
Nginx、Load Balancer、CDNなどでTLSをTerminationしている場合、
Internet
HTTPS
↓
Reverse Proxy
HTTP
↓
Django
という構成があります。
この場合、DjangoへHTTPS情報を正しく伝える必要があります。
ただしSECURE_PROXY_SSL_HEADERを誤設定するとSecurity Problemにつながるため、信頼するProxyがHeaderを確実に削除・再設定する構成でのみ使用してください。
Django公式Documentationも誤ったSECURE_PROXY_SSL_HEADER設定について強い警告を出しています。
対処方法2:「ログイン状態を長く維持する」と「永久Session」を分ける
ここが本記事で最も重要です。
利用者からすると、
毎回Loginするのは面倒
です。
一方Security側からすると、
Sessionが長いほど盗まれたTokenを使える時間も長くなる
という問題があります。
解決方法は、
全部を短くすることでも、全部を長くすることでもありません。
推奨:通常操作と重要操作を分離する
例えば、
ログイン状態
│
├─ 記事閲覧
├─ Comment
├─ Profile閲覧
│
└─ 通常操作
↓
Session継続
一方、
重要操作
│
├─ Email変更
├─ Password変更
├─ MFA解除
├─ Passkey追加
├─ Account削除
├─ API Key発行
├─ Payment情報変更
└─ 管理者権限操作
↓
再認証要求
とします。
これをStep-up AuthenticationまたはReauthenticationとして設計します。
「ログインを維持する」最適解は2つの時間を持つこと
NIST SP 800-63B-4ではSession管理に、
Overall Timeout
Inactivity Timeout
という2種類のTimeoutを定義しています。
Overall Timeout
最後のAuthenticationまたはReauthenticationから、
最大何時間Sessionを認証済みとして扱うか
です。
Inactivity Timeout
最後のActivityから、
何分操作がなければSessionを終了するか
です。
NIST AAL2を参考にすると
NIST SP 800-63B-4ではAAL2について、
Overall Reauthentication Timeout:24時間以内を推奨
Inactivity Timeout:1時間以内を推奨
しています。
ただし、これは米国Federal Digital Identity向けGuidelineです。
一般Webサイトへ24時間・1時間を機械的にコピーする規格ではありません。
筆者の設計方針:Sessionの長さはRisk別に分ける
筆者の考察です。
例えば次のように分類します。
| 操作 | Session方針 |
|---|---|
| 公開記事閲覧 | Login不要 |
| 一般会員機能 | UXを考慮したSession |
| Profile変更 | 再認証 |
| Email変更 | 再認証 |
| Password変更 | 再認証 |
| MFA変更 | 強い再認証 |
| Passkey追加・削除 | 強い再認証 |
| API Token発行 | 強い再認証 |
| 管理画面 | 短めのSession |
| 権限変更 | 強い再認証 |
| 決済関連 | 再認証 |
つまり、
Sessionを長くしたいなら、危険な操作まで同じTrust Levelで長くしない。
という考え方です。
django-allauthには再認証機能がある
現在のdjango-allauthには、
ACCOUNT_REAUTHENTICATION_REQUIRED = True
があります。
有効化するとAccountを変更する前にReauthenticationを要求できます。
また、
ACCOUNT_REAUTHENTICATION_TIMEOUT = 300
がDefaultです。
直近300秒以内にAuthenticationまたはReauthenticationが成功していれば再認証Flowを省略します。
推奨設定例
# settings.py
ACCOUNT_REAUTHENTICATION_REQUIRED = True
# django-allauth defaultは300秒。
# Risk Assessmentに基づき必要なら変更する。
ACCOUNT_REAUTHENTICATION_TIMEOUT = 300
これは特に、
Email変更
Account設定変更
などをSession Theftから守るうえで重要です。
ただし独自CMSの、
管理者権限付与
API Key発行
重要Data Export
決済情報変更
などはApplication独自Viewです。
django-allauthのAccount Reauthentication設定だけで自動的に保護されるとは限りません。
独自View側にもStep-up Authenticationを設計してください。
対処方法3:Active Sessionを利用者自身が確認できるようにする
Session Theftで重要なのは、
「知らない端末がLoginしている」
と利用者自身が確認できることです。
django-allauthにはOptional Appとして、
allauth.usersessions
があります。
公式DocumentationではAuthenticated UserのActive Session一覧を表示し、利用者自身がSessionを終了できる仕組みとして説明されています。
allauth.usersessionsを導入する
settings.py
INSTALLED_APPS = [
# ...
"django.contrib.humanize",
"allauth.usersessions",
]
Activity Trackingも行う場合は、
MIDDLEWARE = [
# ...
"allauth.usersessions.middleware.UserSessionsMiddleware",
]
を追加します。
公式Installation Guideにも同様の設定が記載されています。
Activity Trackingを有効化する
USERSESSIONS_TRACK_ACTIVITY = True
有効にすると、
IP Address
User-Agent
Last Seen
が更新されます。
Migrationを実行する
目的
allauth.usersessions用Database Tableを作成します。
実行場所
Django Project Root。
注意
migrateはDatabase Schemaを変更します。
本番Databaseでは必ずSnapshotまたはDatabase標準Backupを取得してから実行してください。
コマンド
python manage.py migrate
正常例
Applying ... OK
異常例
django.db.utils.OperationalError
またはMigration Conflictが表示される。
判断
Errorが発生した状態で本番運用を続けず、Migration HistoryとDatabase状態を確認します。
User Sessionsをどう見せるか
画面例です。
現在ログイン中の端末
┌─────────────────────────────────────┐
│ Chrome / Windows │
│ Tokyo, Japan │
│ 最終利用:現在 │
│ [現在] │
├─────────────────────────────────────┤
│ Safari / iPhone │
│ Tokyo, Japan │
│ 最終利用:2時間前 │
│ [ログアウト] │
├─────────────────────────────────────┤
│ Chrome / Windows │
│ Unknown Network │
│ 最終利用:10分前 │
│ [ログアウト] │
└─────────────────────────────────────┘
[他のすべてのSessionを終了]
Session IDそのものは画面やLogへ表示しません。
IPアドレスが変わったら即Logoutすべきか
基本的には推奨しません。
正規利用者でも、
Wi-Fi
↓
5G
自宅
↓
会社
VPN ON
↓
VPN OFF
などでIPは変わります。
NISTもSession Monitoringで、
IP Address
Geolocation
Device
Browser
Usage Pattern
Velocity
など複数Signalを評価できるとしています。
したがって、
IPが変わった
=
即攻撃
ではなく、
新しいIP
+
新しいUser-Agent
+
短時間で遠隔地域
+
MFA変更
+
Data Export
のように相関させます。
django-allauthにはSession Client変更Signalもある
USERSESSIONS_TRACK_ACTIVITY=Trueの場合、
allauth.usersessions.signals.session_client_changed
というSignalがあります。
IPやUser-Agentが変わった場合の監視処理へ利用できます。
例えばSOC側では、
Session Client変更
↓
Risk Score計算
↓
通常
├─ 記録のみ
│
高Risk
├─ 再認証
│
Critical
└─ Session終了
という構成にできます。
対処方法4:OAuth Tokenは必要なときだけ保存する
Google、Microsoft、GitHubなどのSocial Loginを利用する場合、
Loginに使いたいだけ
なのか、
Login後もProvider APIを操作したい
のかを分けます。
django-allauthではToken保存はDefault False
現在のdjango-allauth公式設定では、
SOCIALACCOUNT_STORE_TOKENS = False
がDefaultです。
Social Loginだけが必要で、Google DriveなどProvider APIを後から呼ばない場合、
不要なAccess Tokenを長期間保存しない方が攻撃対象を減らせます。
Provider APIを使うならToken管理を別問題として設計する
必要な場合は、
Access Token
Refresh Token
を安全に管理します。
最低限、
最小Scope
短いAccess Token Lifetime
Refresh Token Rotation
Server-side Storage
Database Access Control
TokenをLogへ出さない
Incident時のRevoke
Providerとの連携解除
を設計します。
Refresh Token Rotationとは
Refresh Tokenを、
Token A
↓ 使用
Token B発行
↓
Token A失効
のように使い捨てに近づける方式です。
OAuth 2.0 Security Best Current PracticeであるRFC 9700では、Public ClientへRefresh Tokenを発行する場合、
Sender-constrained Refresh Token
Refresh Token Rotation
のいずれかを要求しています。
Tokenを「盗んでも別端末で使えない」方向へ進化させる
普通のBearer Tokenは、
Tokenを持っている
=
使える
という性質があります。
今後重要になるのが、
Sender-constrained Token
です。
RFC 9449のDPoPは、TokenとCryptographic Keyを結びつけ、盗んだToken単体のReplayを難しくする仕組みです。
NIST SP 800-63B-4も、Device-bound Session CredentialのようなProof-of-Possession方式がSession Secret Theft Riskを軽減する技術として言及しています。
対処方法5:Session IDをlocalStorageへ保存しない
SPAを作ると、
localStorage.setItem("token", token);
と書きたくなる場合があります。
しかしAuthentication TokenをJavaScriptから自由に読めるStorageへ置けば、XSSがToken Theftへ直結しやすくなります。
OWASPはAuthentication Token、Session ID、JWT、Refresh TokenをlocalStorageまたはsessionStorageへ保存しないよう明確に勧告しています。
DjangoのServer-rendered Applicationなら、
HttpOnly
Secure
SameSite
Cookie
を利用したServer-side Sessionは有力な選択肢です。
DjangoのSigned Cookie Session Backendには注意する
Djangoには、
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
があります。
Session DataをSigned CookieとしてClient側へ持たせる方式です。
しかしDjangoのSession DocumentationはCookie BackendについてReplay Riskを明示しています。
盗まれたCookieはServer-side Recordを削除して即座に無効化する方式とは性質が異なり、SESSION_COOKIE_AGEを超えるまでStaleと判定できない場合があります。
したがって、
不審Sessionを即時RevokeしたいApplication
では、Server-side Session Storeを優先して検討した方が扱いやすいです。
推奨するDjango Session構成
一般的なWeb Applicationなら、例えば次の方向です。
# settings.py
SESSION_ENGINE = "django.contrib.sessions.backends.db"
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_DOMAIN = None
SESSION_COOKIE_PATH = "/"
CSRF_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = True
ACCOUNT_REAUTHENTICATION_REQUIRED = True
ACCOUNT_REAUTHENTICATION_TIMEOUT = 300
# Social Login後にProvider APIを使用しないなら
SOCIALACCOUNT_STORE_TOKENS = False
これはあくまでBaseline例です。
Application要件に合わせて調整してください。
SESSION_COOKIE_AGEを短くすれば全部解決するか
解決しません。
Session Lifetimeを短くすれば、盗まれたCookieを使える最大時間を減らせます。
一方、
Token窃取
↓
30秒後にReplay
された場合、1時間Sessionでも7日Sessionでも攻撃は成立し得ます。
したがって、
短いLifetime
だけでなく、
Session Monitoring
+
Reauthentication
+
Remote Revocation
+
Endpoint Security
が必要です。
SESSION_SAVE_EVERY_REQUEST=Trueにも注意する
Djangoでは、
SESSION_SAVE_EVERY_REQUEST = True
とするとRequestごとにSessionを保存し、Cookie Expiryも更新できます。
これはSliding SessionのようなUXを作るときに便利です。
しかし筆者の考察として、Stolen Sessionを攻撃者が継続的に使った場合に、それだけでAbsolute Lifetimeを保証できるわけではありません。
つまり、
操作している限り延長
だけでは、
攻撃者が操作している限り延長
にもなり得ます。
厳密なSecurity Requirementがある場合は、
Inactivity Timeout
+
Absolute Overall Timeout
を別々に設計してください。
NISTもこの2種類を独立したTimeoutとして扱っています。
「ログイン状態を維持する」現実的な設計
おすすめは次の形です。
Login
↓
Passkey / MFA / Code
↓
┌────────────────┐
│ 通常Session │
└───────┬────────┘
│
通常操作│
▼
そのまま利用
│
│重要操作
▼
┌────────────────┐
│ 再認証 │
│ Passkey/MFA等 │
└───────┬────────┘
↓
重要操作許可
これなら、
毎ページでMFAを求めず、危険な操作では本人を再確認できます。
NISTもSession Managementを、毎RequestでCredentialを再提示するより実用的な方式として位置付けています。
Login状態を「強度付き」で考える
さらに一歩進めるなら、
Anonymous
↓
Logged In
↓
Recently Reauthenticated
↓
Strongly Reauthenticated
という状態を持たせます。
例えば、
| Trust Level | 許可する操作 |
|---|---|
| Anonymous | 公開記事 |
| Logged In | Comment・通常機能 |
| Recent Auth | Email・Profile変更 |
| Strong Auth | MFA・Passkey変更 |
| Privileged | Admin操作 |
という設計です。
Sessionを単純な、
Login済み / 未Login
の2値にしないことがポイントです。
Session異常をどう検知するか
Session Theftでは、Password Attackのような、
Login失敗100回
が発生しない場合があります。
そのため認証後を監視します。
見たいSignal
新しいIP
新しいASN
新しいCountry
新しいBrowser
新しいOS
短時間の地理移動
突然の大量Download
突然のAPI呼出
Email変更
MFA変更
Passkey追加
OAuth Consent
Data Export
Privilege Change
NISTはSession MonitoringでIP、Location、Browser、Device、Usage Pattern、TimingなどをRisk Signalとして扱えるとしています。
EDR側でもBrowser Credential窃取を見る
Server側だけでは、
なぜSession Cookieが盗まれたのか
が分からない場合があります。
Endpoint側では、
Browser Cookie Databaseへの不審Access
Browser Process MemoryへのRead
Information Stealer
Browser Extension
Credential Dump
などを監視します。
MITREのT1539 Detection StrategyはBrowser Cookie StorageやBrowser Process Memoryへの不審AccessをDetection Sourceとして挙げています。
Session IDをLogへそのまま記録してはいけない
Session調査のため、
sessionid=abcdef...
をApplication Logへ丸ごと保存するのは避けます。
Logが盗まれた場合、
LogそのものがCredential Storeになります。
必要なら、
Session DB内部ID
Hash化したCorrelation ID
UserSession Model ID
など、Replayに利用できないIdentifierを使います。
動作確認1:Cookie属性をBrowserで確認する
Chrome DevToolsの場合、
Application
↓
Storage
↓
Cookies
を確認します。
見る項目は、
| 項目 | 期待値 |
|---|---|
| Secure | 有効 |
| HttpOnly | 有効 |
| SameSite | LaxまたはStrict |
| Domain | 必要最小限 |
| Path | 必要最小限 |
| Expiration | 設計どおり |
です。
NISTもSession Cookieを最小限のHost/Pathへ限定し、Secure・HttpOnly・SameSiteを利用するよう推奨しています。
動作確認2:重要操作で再認証されるか
Login後、
Email変更
Password変更
MFA変更
へ移動します。
期待値は、
通常Session
↓
重要操作
↓
再認証要求
↓
成功
↓
変更可能
です。
ACCOUNT_REAUTHENTICATION_REQUIRED=Trueを設定した場合、django-allauthのAccount変更Flowでこの動作を確認します。
動作確認3:Remote Logoutを確認する
PCとスマートフォンの2端末でLoginします。
PC
+
Smartphone
User Sessions画面からSmartphone Sessionを終了します。
その後Smartphoneで認証が必要になることを確認します。
これは攻撃を再現するTestではなく、自分のAccountを使った安全なFunction Testです。
Session Tokenを盗まれた疑いがある場合の対処
Passwordだけ変更して終了しないでください。
次の順番で対応します。
Session Theft疑い
↓
対象User特定
↓
Active Session失効
↓
OAuth Access Token失効
↓
Refresh Token失効
↓
不明なMFA Method削除
↓
不明なPasskey削除
↓
OAuth Consent確認
↓
Password再発行
↓
端末調査
↓
Information Stealer確認
↓
安全な端末から再Login
なぜPassword変更だけでは不十分なのか
Django Session、Google Session、Microsoft Session、OAuth Tokenなどは別々のLifecycleを持つ場合があります。
NISTもFederationではIdPとRPがSessionを独立して管理し、一方のSession終了が他方のSession終了と必ず連動するわけではないと説明しています。
そのためIncident Responseでは、
Password
Django Session
IdP Session
Access Token
Refresh Token
OAuth Consent
MFA Method
を別々に確認します。
再発防止1:AuthenticationとSessionを別々にThreat Modelする
Security Reviewで、
Passwordは安全か?
MFAはあるか?
だけを確認しないでください。
次まで確認します。
Sessionはどこに保存される?
Sessionは何時間有効?
誰がSessionを失効できる?
全Session Logoutはある?
CookieはJavaScriptから読める?
OAuth Tokenはどこに保存される?
Refresh TokenはRotationされる?
重要操作で再認証する?
Session異常を検知できる?
再発防止2:端末侵害もIdentity侵害として扱う
Session CookieはBrowserに存在します。
したがって、
Endpoint Security
と、
Identity Security
は別問題ではありません。
Information StealerにBrowser Sessionを盗まれれば、Web側で強いMFAを設定していても影響を受けます。
Cloudflare 2026 Threat Reportが強調しているのもこの点です。
再発防止3:Security Eventを通知する
例えば、
新しいLogin
新しいPasskey登録
MFA解除
Password変更
Email変更
Session終了
Recovery Code再生成
などを利用者へ通知します。
django-allauthにはAccount関連Security NotificationをEmailする設定もあり、現在のConfigurationでは、
ACCOUNT_EMAIL_NOTIFICATIONS = False
がDefaultです。
利用環境に応じて通知設計を検討します。
注意点・よくある誤解
誤解1:Passkeyを入れればSession Hijackingも防げる
違います。
PasskeyはAuthenticationを強くできます。
しかし一般的なBearer Session Cookieが既に発行された後、そのCookieを盗んだ攻撃者へ毎Request Passkeyを要求するわけではありません。
Passkey
=
入口防御
Session Security
=
入場後防御
として分けてください。
誤解2:HttpOnlyを付ければXSSは問題ない
違います。
HttpOnlyはJavaScriptからCookieそのものを読むRiskを減らします。
しかしXSSを使いBrowser内から認証済みRequestを送らせる可能性までは消えません。
OWASPもHttpOnlyはCookie Confidentialityを守る対策であり、XSS自体を無効化するものではないと説明しています。
誤解3:Sessionを5分にすれば安全
現実的ではありません。
安全性は上がる場合がありますが、利用者が5分ごとにLoginさせられればUXが大幅に悪化します。
さらに、Cookieを盗まれて直後にReplayされれば5分でも攻撃は可能です。
短いSessionだけを唯一のDefenseにしないことが重要です。
誤解4:IPが変わったら即Logoutすれば安全
誤検知が増えます。
Mobile回線、VPN、Corporate NATなどではIP変更が正常に起こります。
IPは、
Risk Signal
として他のSignalと組み合わせる方が適切です。NISTも複数のSession Characteristicsを組み合わせる考え方を示しています。
誤解5:「Trust this browser」は必ず安全
便利ですがTrade-offがあります。
MFAを毎回要求しない仕組みはUXを改善します。
一方でTrust情報やSessionが利用できる期間が長くなれば、端末盗難やMalware侵害時の影響時間も検討する必要があります。
django-allauthにもMFAの「Trust this browser?」機能がありますが、利用する場合は一般Accountと管理AccountでPolicyを分けることを推奨します。現在のRelease Notesにもこの機能が記載されています。
批判的に見るべきポイント
1. 「MFAを入れたからIdentity Securityは完成」という考えは古い
MFAは依然として必須級のDefenseです。
しかしM-Trends 2026やCloudflare 2026 Threat Reportが示しているように、攻撃者はAuthentication後のTokenへ狙いを移しています。
MFAの価値がなくなったのではありません。
Attack SurfaceがMFAの後ろまで広がったと考える方が正確です。
2. Sessionを短くしすぎる設計も失敗する
Securityだけを考えて、
毎回Login
毎回MFA
とすると利用者は不便になります。
NISTも、Credentialを継続的に毎回提示させるよりSession Managementを利用する方が望ましいとしています。
SecurityとUXの両方を考え、
通常操作
=
Session
重要操作
=
Reauthentication
と分ける方が実用的です。
3. Serverだけ守ってもSession Theftを止められない
Information StealerによるCookie TheftはClient Endpointで発生します。
そのため、
WAF
+
Django Security
だけでは不十分です。
企業環境なら、
EDR
Browser Security
DNS Security
Identity Monitoring
Django Session Monitoring
まで一体で考える必要があります。
4. Tokenを長寿命化して「Remember Me」を作るだけでは危険
単純に、
SESSION_COOKIE_AGE = 90日
とするだけでは、盗まれたSessionの価値まで長くなります。
Remember Meを提供する場合でも、
通常閲覧は継続
+
重要操作は再認証
+
Remote Session Revocation
+
Risk Monitoring
を組み合わせた方が安全です。
ログイン済み状態を安全に維持する最終設計
本記事の設計をまとめると、
User
│
▼
┌──────────────────┐
│ Authentication │
│ │
│ Login by Code │
│ Passkey │
│ TOTP │
│ WebAuthn │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Django Session │
│ │
│ Secure │
│ HttpOnly │
│ SameSite │
│ Server-side │
└────────┬─────────┘
│
┌────────┴────────┐
│ │
▼ ▼
通常操作 重要操作
│ │
│ ▼
│ Reauthentication
│ │
└────────┬────────┘
│
▼
Session Monitoring
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
Normal Suspicious Critical
│ │ │
│ ▼ ▼
│ Reauth Revoke
│
▼
継続利用
これが、
「安全性を上げながら、毎回Loginさせない」
ための基本形です。
まとめ
これまでAuthentication Securityでは、
Passwordを守る
↓
MFAを追加する
↓
Passkeyへ移行する
という進化が中心でした。
しかし2026年は、その次を見る必要があります。
Authentication
↓
Session発行
↓
Session維持
↓
重要操作
までを一つのSecurity Lifecycleとして扱います。
M-Trends 2026では長寿命OAuth TokenやSession Cookieの窃取が、MFA後のSessionをHijackする重要な問題として扱われています。Cloudflareの2026 Threat ReportもInformation StealerによるActive Session Token窃取を取り上げています。
以前の記事で扱った、
Day 08
Login by Code
と、
Day 09
Passkey / TOTP / WebAuthn
はAuthenticationの入口を強くします。
本記事で加わるのが、
Day 10相当
Session Security
です。
最終的には、
強いAuthentication
+
Secure Session Cookie
+
適切なSession Lifetime
+
重要操作のReauthentication
+
Active Session管理
+
Remote Logout
+
OAuth Token最小化
+
Session Monitoring
+
Endpoint Detection
まで考える必要があります。
最も重要なのは、
「ログインできた人を永久に信用する」のではなく、「ログイン後も必要な範囲で信用を更新する」
という考え方です。
FAQ
Q. Passwordを変更すれば盗まれたSession Cookieも必ず無効になりますか?
すべてのサービスについて必ず無効になるとは限りません。
Django Session、IdP Session、OAuth Access Token、Refresh Tokenなどは別のLifecycleを持つ場合があります。
Incident時はPasswordだけでなくActive SessionとOAuth Tokenも確認してください。NISTもFederation環境ではIdPとRPのSessionが独立して管理されることを説明しています。
Q. PasskeyならSession Cookieは不要ですか?
通常のWeb Applicationでは、PasskeyでAuthenticationした後も継続利用のためSessionを使用します。
Passkeyを毎HTTP Requestで実行するわけではありません。
Q. DjangoのSession CookieにPasswordは入っていますか?
通常のServer-side Django Sessionでは、Browser側CookieはSessionを識別するための値として利用され、Application DataはServer-side Session Storeで管理します。
Session ID自体がAuthentication済み状態へつながるため、Passwordと同様に秘密情報として扱う必要があります。
Q. SESSION_COOKIE_AGEはいくつが正解ですか?
万能な値はありません。
ApplicationのRisk、利用者、端末、扱う情報、Reauthentication設計によって変わります。
NIST AAL2では24時間Overall、1時間InactivityがReferenceになりますが、一般Web Applicationへそのままコピーする基準ではありません。
Q. Session CookieをRedisへ保存すれば安全ですか?
StorageをRedisへ変更するだけでSession Theftは解決しません。
Server-side Revokeがしやすい利点はありますが、
Cookie Theft
XSS
Endpoint Malware
Session Monitoring
Lifetime
は別途対策が必要です。
Q. localStorageにJWTを保存してはいけませんか?
Authentication TokenやRefresh TokenをlocalStorageへ置く設計はXSS時のToken Theft Riskがあります。
OWASPはAuthentication Token、Session ID、JWT、Refresh TokenなどをlocalStorageやsessionStorageへ保存しないよう勧告しています。
Q. Social LoginならOAuth Tokenを保存する必要がありますか?
Loginだけが目的なら必ずしも必要ではありません。
django-allauthのSOCIALACCOUNT_STORE_TOKENSは現在DefaultでFalseです。Provider APIを利用する要件がある場合のみ保存の必要性を検討してください。
Q. Session TheftをWAFで防げますか?
完全には防げません。
盗まれた正規Session Cookieを使ったRequestは、Protocol上は通常の認証済みRequestに見える場合があります。
WAFに加え、
Identity Monitoring
User Sessions
Reauthentication
EDR
Behavior Detection
が重要になります。
参考情報
関連記事:Login by Code
10日で作る Django CMS Day 08:django-allauth Login by Code
https://kurutann.com/diary/detail/django-cms-day-08-allauth-login-by-code/
関連記事:Passkey / TOTP / WebAuthn
10日で作る Django CMS Day 09:Passkey / TOTP / WebAuthn
https://kurutann.com/diary/detail/django-cms-day-09-passkey-totp-webauthn/
Mandiant / Google Cloud
M-Trends 2026 Report: Executive Edition
https://cloud.google.com/security/resources/m-trends-executive-edition?hl=ja
M-Trends 2026: Data, Insights, and Strategies From the Frontlines
https://cloud.google.com/blog/topics/threat-intelligence/m-trends-2026
Cloudflare
2026年Cloudflare脅威レポート
https://blog.cloudflare.com/ja-jp/2026-threat-report/
英語版:
https://blog.cloudflare.com/2026-threat-report/
MITRE ATT&CK
T1539 – Steal Web Session Cookie
https://attack.mitre.org/techniques/T1539/
T1528 – Steal Application Access Token
https://attack.mitre.org/techniques/T1528/
Detection of Web Session Cookie Theft
https://attack.mitre.org/detectionstrategies/DET0509/
NIST SP 800-63B-4
Session Management
https://pages.nist.gov/800-63-4/sp800-63b/session/
Authentication Assurance Levels
https://pages.nist.gov/800-63-4/sp800-63b/aal/
Threats and Security Considerations
https://pages.nist.gov/800-63-4/sp800-63b/security/
Django
Django 5.2 Settings
https://docs.djangoproject.com/ja/5.2/ref/settings/
Security in Django
https://docs.djangoproject.com/ja/5.2/topics/security/
System Check Framework
https://docs.djangoproject.com/en/5.2/ref/checks/
django-allauth
Account Configuration
https://docs.allauth.org/en/latest/account/configuration.html
MFA
https://docs.allauth.org/en/latest/mfa/
WebAuthn
https://docs.allauth.org/en/latest/mfa/webauthn.html
User Sessions
https://docs.allauth.org/en/latest/usersessions/
User Sessions Installation
https://docs.allauth.org/en/latest/usersessions/installation.html
Social Account Configuration
https://docs.allauth.org/en/latest/socialaccount/configuration.html
OAuth Security
RFC 9700 – Best Current Practice for OAuth 2.0 Security
https://www.rfc-editor.org/rfc/rfc9700.html
RFC 9449 – OAuth 2.0 Demonstrating Proof of Possession
https://www.rfc-editor.org/rfc/rfc9449.html
OWASP
Session Management Cheat Sheet
https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
HTML5 Security Cheat Sheet
https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html
コメント(0件)
まだコメントはありません。最初のコメントを投稿してください!