2026年はパスワードより「ログイン済み」が狙われる――Session Cookie・OAuth Token窃取とDjangoの安全なセッション設計

2026年はパスワードより「ログイン済み」が狙われる――Session Cookie・OAuth Token窃取とDjangoの安全なセッション設計
目次

最終確認日: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として扱う方がよい。


この記事で分かること

この記事では次の順番で説明します。

  1. なぜ「ログイン済み」が狙われるのか

  2. Session Cookieとは何か

  3. OAuth Tokenとの違い

  4. Login by CodeとSession保護の関係

  5. Passkey・TOTP・WebAuthnとSession保護の関係

  6. Session Tokenが盗まれる経路

  7. 自分のDjangoサイトを確認する方法

  8. Session Cookieを安全に設定する方法

  9. ログイン状態を安全に維持する方法

  10. django-allauthで再認証を入れる方法

  11. User Sessionsを利用者へ見せる方法

  12. OAuth Tokenをどう管理するか

  13. 異常Sessionをどう検知するか

  14. Session Token流出時の対処

  15. 再発防止


対象読者・前提環境

主な対象は次の方です。

  • 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 CookieWeb Applicationでログイン状態を維持Django sessionid
OAuth Access TokenAPIへ権限を持ってアクセスGoogle / Microsoft API Token
Refresh Token新しいAccess Tokenを取得OAuth Refresh Token
API TokenAPI認証GitHub PATなど
Passkey本人AuthenticationWebAuthn Credential
TOTPMFA6桁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などをlocalStoragesessionStorageへ保存しないよう勧告しています。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_SECURECSRF_COOKIE_SECURETrueへ設定するよう推奨しています。


確認方法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管理に、

  1. Overall Timeout

  2. 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には、

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 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 InComment・通常機能
Recent AuthEmail・Profile変更
Strong AuthMFA・Passkey変更
PrivilegedAdmin操作

という設計です。

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有効
SameSiteLaxまたは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と同様に秘密情報として扱う必要があります。


万能な値はありません。

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などをlocalStoragesessionStorageへ保存しないよう勧告しています。


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

読んだ内容を10問練習と実技で確認

記事で理解した用語を、StudyQuestの演習とクラウド実技ラボで定着させます。

10問練習 実技ラボ

コメント(0件)

まだコメントはありません。最初のコメントを投稿してください!

コメントを投稿