[Android] WebViewのCookie完全ガイド — 属性の意味から追加・削除・確認まで

WebViewでCookieを扱うとき、「とりあえずsetCookieで動いたからOK」で済ませていませんか?実はCookieにはMax-AgeSecureHttpOnlySameSiteといった属性がいろいろあって、それぞれに大事な意味があります。

この記事では、Cookieの各属性が何を意味しているのかをわかりやすく解説しつつ、WebViewでのCookieの追加・削除・確認の方法をサンプルコード付きで紹介していきます。

※ CookieManagerの基本的な使い方はこちらの記事でも紹介しているので、合わせてどうぞ。

スポンサーリンク

前提条件

  • Android Studio(最新安定版推奨)
  • minSdk 21 以上
  • Kotlin

Cookieの基本構造

まず、Cookieがどんな形をしているのか見てみましょう。ブラウザやWebViewに保存されるCookieは、こんな文字列になっています。

session_id=abc123; Max-Age=3600; Path=/; Secure; HttpOnly; SameSite=Lax

セミコロン区切りで、最初のsession_id=abc123がCookieの名前と値、それ以降が属性です。これらの属性を理解しておくと、「なぜかCookieが送信されない」「いつの間にかCookieが消えてる」といった問題のデバッグがぐっと楽になります。

Cookie属性の意味を理解する

ここからが本題です。Cookieに付けられる属性を一つずつ見ていきましょう。

Max-Age と Expires — いつまで保持するか

Cookieの有効期限を決める属性です。

  • Max-Age=3600 — 設定から3600秒(1時間)で期限切れ
  • Expires=Thu, 01 Jan 2026 00:00:00 GMT — 指定した日時に期限切れ

どちらも指定しなかった場合はセッションCookieになり、WebViewが閉じられるとCookieは消えます。ログインセッションなど「ブラウザを閉じたら消えてほしい」ケースではあえて指定しないこともあります。

両方指定された場合はMax-Ageが優先されます。今はMax-Ageを使うのが一般的ですね。

// Max-Ageを指定してCookieをセットする例(1時間有効)
val cookieManager = CookieManager.getInstance()
cookieManager.setCookie(
    "https://example.com",
    "session_id=abc123; Max-Age=3600; Path=/"
)

Path — どのパスに送信するか

Cookieを送信するURLのパスを制限する属性です。

  • Path=/ — サイト全体のどのページにもCookieが送信される
  • Path=/api/api以下のURLにだけCookieが送信される

特に理由がなければPath=/を使っておけばOKです。

Domain — どのドメインに送信するか

Cookieを送信する対象のドメインを指定します。

  • Domain=example.comexample.comと、そのサブドメイン(api.example.comなど)にもCookieが送信される
  • 指定なし — Cookieを発行したドメインにだけ送信される(サブドメインには送信されない)

セキュリティ的には、必要以上に広いドメインを指定しないのがベターです。

Secure — HTTPSだけに限定する

Secure属性が付いたCookieは、HTTPS通信のときだけ送信されます。HTTP(暗号化なし)では送信されません。

// Secure属性付きのCookie
cookieManager.setCookie(
    "https://example.com",
    "token=xyz789; Secure; Path=/"
)

ログイン情報やトークンを含むCookieには必ず付けましょう。付けないと、中間者攻撃でCookieが盗まれるリスクがあります。

HttpOnly — JavaScriptからアクセスさせない

HttpOnly属性が付いたCookieは、JavaScriptのdocument.cookieからは読み取れなくなります。HTTP通信(リクエストヘッダー)にだけ含まれます。

XSS(クロスサイトスクリプティング)攻撃でCookieが盗まれるのを防ぐための属性です。セッション情報を含むCookieには基本的に付けておくべきです。

ただし注意点として、WebViewのCookieManager.getCookie()ではHttpOnlyのCookieも取得できます。ブラウザのJavaScriptからは見えなくても、Androidのネイティブコードからはアクセスできるということですね。

SameSite — クロスサイトリクエストの制御

SameSiteはCSRF(クロスサイトリクエストフォージェリ)攻撃を防ぐための属性で、3つの値があります。

  • SameSite=Strict — 同一サイトからのリクエストにだけCookieを送信する。一番厳しい
  • SameSite=Lax — 基本は同一サイトのみだが、外部サイトからのGETリクエスト(リンクのクリックなど)ではCookieを送信する。デフォルト値
  • SameSite=None — クロスサイトでもCookieを送信する。ただしSecure属性が必須
// SameSite=None を設定する場合(Secureも必須)
cookieManager.setCookie(
    "https://example.com",
    "tracking=abc; SameSite=None; Secure; Path=/"
)

WebViewでサードパーティのサイトを表示する場合、SameSite=Lax(デフォルト)だとCookieが送信されないケースがあります。「ログイン状態が維持されない」という問題に遭遇したら、この属性を確認してみてください。

WebViewでのCookie操作

属性がわかったところで、WebViewでCookieを実際に操作する方法を見ていきましょう。すべてCookieManagerクラスを使います。

Cookieの追加(セット)

基本はsetCookie(url, cookieString)です。属性も含めた文字列をそのまま渡します。

val cookieManager = CookieManager.getInstance()
 
// Cookieの受け入れを有効にする(忘れがち!)
cookieManager.setAcceptCookie(true)
 
// シンプルなセッションCookie
cookieManager.setCookie(
    "https://example.com",
    "user_name=taro"
)
 
// 属性付きのCookie
cookieManager.setCookie(
    "https://example.com",
    "session_id=abc123; Max-Age=86400; Path=/; Secure; HttpOnly; SameSite=Lax"
)
 
// 変更をディスクに永続化する
cookieManager.flush()

flush()は忘れがちですが重要です。これを呼ばないと、アプリが強制終了した場合にCookieが保存されないことがあります。

Cookieの取得(確認)

getCookie(url)で、指定したURLに紐づくCookieを取得できます。

val cookies = cookieManager.getCookie("https://example.com")
// 結果例: "user_name=taro; session_id=abc123"

戻り値は名前=値がセミコロン区切りで並んだ文字列です。属性情報は含まれません。Cookieがない場合はnullが返ります。

特定のCookieだけ取り出したい場合は、文字列をパースする必要があります。拡張関数を用意しておくと便利です。

/**
 * CookieManagerから特定の名前のCookieの値を取得する
 */
fun CookieManager.getCookieValue(url: String, name: String): String? {
    val cookies = getCookie(url) ?: return null
    return cookies.split(";")
        .map { it.trim() }
        .firstOrNull { it.startsWith("$name=") }
        ?.substringAfter("=")
}
 
// 使い方
val sessionId = cookieManager.getCookieValue(
    "https://example.com",
    "session_id"
)
// 結果: "abc123"

特定のCookieの削除

AndroidのCookieManagerには「このCookieを削除して」というメソッドがありません。ちょっとトリッキーですが、同じ名前のCookieを有効期限切れで上書きすることで削除します。

/**
 * 指定した名前のCookieを削除する
 */
fun CookieManager.removeCookie(url: String, name: String) {
    // Max-Age=0 で即期限切れにする
    setCookie(url, "$name=; Max-Age=0; Path=/")
    flush()
}
 
// 使い方
cookieManager.removeCookie("https://example.com", "session_id")

Max-Age=0を指定すると、Cookieは即座に期限切れになって削除されます。Pathは元のCookieと同じものを指定しないと削除されないので注意してください。

すべてのCookieの削除

全Cookieを一括削除したい場合は、専用のメソッドがあります。

// すべてのCookieを削除
cookieManager.removeAllCookies { success ->
    // success: 削除が実行されたかどうか
    if (success) {
        Log.d("Cookie", "すべてのCookieを削除しました")
    }
}
cookieManager.flush()

コールバックが非同期で返ってくる点に注意してください。ログアウト処理などで使う場合は、コールバックを待ってから次の処理に進むのが安全です。

セッションCookieだけ削除

有効期限が設定されていない(セッションCookieの)ものだけ削除したい場合は、removeSessionCookiesを使います。

// セッションCookieだけ削除(有効期限付きのCookieは残る)
cookieManager.removeSessionCookies { success ->
    Log.d("Cookie", "セッションCookie削除: $success")
}
cookieManager.flush()

実践:ログインセッション管理の例

ここまでの内容を使って、よくあるログインセッション管理のパターンをまとめてみましょう。

class WebViewActivity : AppCompatActivity() {
 
    private lateinit var webView: WebView
    private val cookieManager = CookieManager.getInstance()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_webview)
 
        webView = findViewById(R.id.webView)
        setupCookies()
        setupWebView()
        webView.loadUrl("https://example.com/login")
    }
 
    private fun setupCookies() {
        // Cookieを有効にする
        cookieManager.setAcceptCookie(true)
 
        // サードパーティCookieも許可する場合
        cookieManager.setAcceptThirdPartyCookies(webView, true)
    }
 
    private fun setupWebView() {
        webView.settings.javaScriptEnabled = true
 
        webView.webViewClient = object : WebViewClient() {
            override fun onPageFinished(view: WebView?, url: String?) {
                super.onPageFinished(view, url)
                // ページ読み込み完了後にCookieを確認
                url?.let { checkLoginStatus(it) }
            }
        }
    }
 
    private fun checkLoginStatus(url: String) {
        val sessionId = cookieManager.getCookieValue(url, "session_id")
        if (sessionId != null) {
            Log.d("Cookie", "ログイン済み: session_id=$sessionId")
        } else {
            Log.d("Cookie", "未ログイン")
        }
    }
 
    fun logout() {
        // セッションCookieを削除
        cookieManager.removeCookie(
            "https://example.com",
            "session_id"
        )
        // ログインページにリダイレクト
        webView.loadUrl("https://example.com/login")
    }
 
    override fun onDestroy() {
        // Cookieの変更をディスクに保存
        cookieManager.flush()
        webView.destroy()
        super.onDestroy()
    }
}

便利な拡張関数まとめ

この記事で紹介した拡張関数をまとめたユーティリティファイルです。プロジェクトにコピーして使ってください。

// CookieExtensions.kt
 
import android.webkit.CookieManager
 
/**
 * 指定URLの特定のCookieの値を取得する
 */
fun CookieManager.getCookieValue(
    url: String,
    name: String
): String? {
    val cookies = getCookie(url) ?: return null
    return cookies.split(";")
        .map { it.trim() }
        .firstOrNull { it.startsWith("$name=") }
        ?.substringAfter("=")
}
 
/**
 * 指定URLの特定のCookieを削除する
 */
fun CookieManager.removeCookie(
    url: String,
    name: String,
    path: String = "/"
) {
    setCookie(url, "$name=; Max-Age=0; Path=$path")
    flush()
}
 
/**
 * 指定URLのCookieをMap形式で取得する
 */
fun CookieManager.getCookieMap(
    url: String
): Map<String, String> {
    val cookies = getCookie(url) ?: return emptyMap()
    return cookies.split(";")
        .map { it.trim() }
        .filter { it.contains("=") }
        .associate { cookie ->
            val (key, value) = cookie.split("=", limit = 2)
            key.trim() to value.trim()
        }
}
 
/**
 * 属性付きでCookieをセットするビルダー
 */
fun CookieManager.setCookieWithAttributes(
    url: String,
    name: String,
    value: String,
    maxAge: Int? = null,
    path: String = "/",
    secure: Boolean = false,
    httpOnly: Boolean = false,
    sameSite: String? = null // "Strict", "Lax", "None"
) {
    val builder = StringBuilder("$name=$value; Path=$path")
    maxAge?.let { builder.append("; Max-Age=$it") }
    if (secure) builder.append("; Secure")
    if (httpOnly) builder.append("; HttpOnly")
    sameSite?.let { builder.append("; SameSite=$it") }
    setCookie(url, builder.toString())
    flush()
}

特にsetCookieWithAttributesは、属性の文字列を手動で組み立てるミスを防げるので便利です。

// 使い方
cookieManager.setCookieWithAttributes(
    url = "https://example.com",
    name = "session_id",
    value = "abc123",
    maxAge = 86400,      // 1日
    secure = true,
    httpOnly = true,
    sameSite = "Lax"
)

よくあるトラブルと対処法

Cookieがセットされない

setAcceptCookie(true)を呼び忘れていませんか?デフォルトでtrueですが、明示的に呼んでおくと安心です。また、setCookieのURLにはスキーム(https://)を含める必要があります。

サードパーティCookieが送信されない

Android 5.0以降、サードパーティCookieはデフォルトで無効です。setAcceptThirdPartyCookies(webView, true)で有効にする必要があります。

cookieManager.setAcceptThirdPartyCookies(webView, true)

アプリ再起動でCookieが消える

flush()を呼んでいない可能性があります。Cookieの変更後は必ずflush()でディスクに書き込みましょう。

getCookieでnullが返る

URLの形式が間違っている可能性があります。setCookieで使ったURLと同じ形式でgetCookieを呼んでいるか確認してみてください。特にスキーム(http/https)やパスの違いに注意です。

まとめ

Cookieの属性とWebViewでの操作方法をまとめると、こんな感じです。

  • Max-Age / Expires — Cookieの有効期限。指定なしだとセッションCookieになる
  • Secure — HTTPSのときだけ送信。トークン系には必須
  • HttpOnly — JavaScriptからアクセス不可にする。XSS対策
  • SameSite — クロスサイトリクエストの制御。デフォルトはLax
  • Path / Domain — 送信先の制限

WebViewでの操作はCookieManagerに集約されていて、追加はsetCookie、取得はgetCookie、削除はMax-Age=0で上書き、というパターンを覚えておけば大丈夫です。

この記事で紹介した拡張関数を使えば、Cookie操作がかなり楽になるはずです。ぜひプロジェクトに取り入れてみてくださいね。

コメント

タイトルとURLをコピーしました