0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WKWebView で SNS を開く iOS アプリを、パスワードと閲覧内容に触れずに作る:ログイン・Cookie・注入スクリプト・エラーログの4か所

0
Last updated at Posted at 2026-10-27

minulo は、Instagram や YouTube のウェブ版を WKWebView で開いてリールやショートを消す iPhone アプリで、利用者の ID・パスワードや見ていたページを開発者が受け取らない形で作っています(消す機能は無料)。ログインは SNS のページで直接させ、Cookie は iOS がアプリごとに分けた場所に置き、ページに入れるスクリプトは何も外へ送らず、エラーログからは URL と名前を消してから送ります。この記事では、この4か所の作り方を書きます。(この記事は minulo の開発者が書いています)

minulo の作者の北林です。同じ形のアプリを作る人向けに、4か所それぞれの注意点を書きます。コードは説明用の例で、minulo のものそのままではありません。リールを隠す側の話は YouTube のショートの記事 に書いたので、ここでは「触らない」ことに絞ります。

1. ログインは SNS のページで、アプリは欄を読まない

ネイティブの欄で ID とパスワードを受け取り、JS でフォームに流し込む作りにはしません。SNS のログインページをそのまま出し、そこで入力してもらいます。値はページから SNS へ直接送られ、アプリのコードは通りません。

気をつけるのは途中で開くページです。Google や Apple のログイン、reCAPTCHA の埋め込みを止めると通りません。decidePolicyFor で開く先を絞るときは、メインフレームと iframe を分けます。

func webView(_ webView: WKWebView,
             decidePolicyFor action: WKNavigationAction,
             decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    guard let host = action.request.url?.host else { return decisionHandler(.cancel) }
    let isMain = action.targetFrame?.isMainFrame ?? true
    if isMain {
        // 表示してよいのは対応する SNS とログイン画面だけ
        decisionHandler(allowedMainHosts.contains { host == $0 || host.hasSuffix("." + $0) } ? .allow : .cancel)
    } else {
        // reCAPTCHA など、ログインに要る埋め込みは通す
        decisionHandler(.allow)
    }
}

新しいウィンドウを開くリンクは WKUIDelegate の createWebViewWith でも同じ判定をします。

2. Cookie は iOS がアプリごとに分けた場所に置く

WKWebsiteDataStore.default() を使うと、Cookie はそのアプリの中だけに保存され、ほかのアプリからは読めません。サーバーに同期しないので、開発者には届きません。

やりがちなのは、httpCookieStore から Cookie を取り出して自前で保存し直すことです。守らなくていい値を自分のコードが持つことになります。ストアに任せて触らないのが安全です。

3. 注入スクリプトは分けた世界で動かし、送り口を作らない

おすすめかどうかを見分けるため、スクリプトは画面の文字を読みます。読むこと自体は避けられないので、外へ出す道を作らないことで守ります。

  • WKUserScript は WKContentWorld.world(name:) でページと別の世界で動かす。ページ側からこちらの変数に手が届かなくなります。
  • WKScriptMessageHandler で読んだ文字や URL を渡す口は作らない。要るなら「隠した数」だけにします。
  • 注入スクリプトから自分のサーバーへ fetch しない。

4. エラーログからは URL・@名前・メールを消してから送る

見落としやすいのがエラーログです。例外の文には、開いていたページの URL や @ユーザー名 が混ざります。そのまま送ると閲覧の記録を送るのと同じです。minulo は送る前に URL・@ の名前・メールを伏せ、長さも切っています。

func scrub(_ s: String) -> String {
    var t = s
    t = t.replacingOccurrences(of: #"\b[a-z][\w+.-]*://\S+"#, with: "<url>", options: [.regularExpression, .caseInsensitive])
    t = t.replacingOccurrences(of: #"[\w.+-]+@[\w-]+\.[\w.-]+"#, with: "<email>", options: .regularExpression)
    t = t.replacingOccurrences(of: #"@[\w.]{2,}"#, with: "@<name>", options: .regularExpression)
    return String(t.prefix(160))
}

メールを先に消すのがコツです。逆にすると @ 以降だけが名前として消え、アドレスの前半が残ります。http 以外のスキームも伏せます。

App Store の「アプリのプライバシー」は開発者の申告なので、申告と実際に送るものが合っているかは自分で確かめます。

公式アプリはどうするか

iPhone では、公式アプリの中は変えられません。minulo は公式アプリを Screen Time(FamilyControls)で止めます。選んだアプリは中身の分からないトークンで渡るので、ここでも開発者には分かりません。

minulo の限界

作者として限界も書きます(minulo にできないこと より)。

  • リールやショートが消えるのは minulo の中で開いたときだけで、公式アプリの中は変わりません。
  • 公式アプリを止める機能は、無料では1回2時間までです。毎晩の予定で自動で止めるのは Pro です。
  • 公式アプリを止めている間は、そのアプリの通知が届きません。minulo 自身も通知を送りません。
  • ストーリーズの投稿・ボイスメッセージ・通話は、ウェブ版に無いので使えません。
  • iPhone のアプリで、Mac 版と Chrome・Safari の機能拡張もあります。Android 版はありません。

送るもの・残すものの全体は minulo は安全? にあります。別のやり方をしている方の話も聞きたいです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?