RORK LABEN
NATIVE — Rork Max では AR / LiDAR スキャン、Metal を使った 3D、Dynamic Island、Siri Intents、HealthKit、NFC、App Clips、Core ML まで届きますIOS27 — iOS 27 Developer Beta 4 で Siri AI の対象が iPhone 15 Pro / 15 Pro Max・16 シリーズ・17 系へ広がり、応答が初期ベータより明確に速くなりましたDESIGN — iOS・iPadOS・macOS 27 向けの Apple 純正デザインキットが Figma と Sketch 向けに公開されました。新 OS の UI を組む段階で参照できますCANARY — Android 17(Cinnamon Bun)は従来の Developer Preview を廃し、継続更新される Canary ビルドへ移行しました。検証のタイミング設計が変わりますSPARK — Android 17 の Gemini Spark は、アプリを直接操作して配車の予約や注文といった複合的な作業を自動化しますSCALE — Rork は a16z から $2.8M を調達し、月間訪問は74万件規模まで伸びていますNATIVE — Rork Max では AR / LiDAR スキャン、Metal を使った 3D、Dynamic Island、Siri Intents、HealthKit、NFC、App Clips、Core ML まで届きますIOS27 — iOS 27 Developer Beta 4 で Siri AI の対象が iPhone 15 Pro / 15 Pro Max・16 シリーズ・17 系へ広がり、応答が初期ベータより明確に速くなりましたDESIGN — iOS・iPadOS・macOS 27 向けの Apple 純正デザインキットが Figma と Sketch 向けに公開されました。新 OS の UI を組む段階で参照できますCANARY — Android 17(Cinnamon Bun)は従来の Developer Preview を廃し、継続更新される Canary ビルドへ移行しました。検証のタイミング設計が変わりますSPARK — Android 17 の Gemini Spark は、アプリを直接操作して配車の予約や注文といった複合的な作業を自動化しますSCALE — Rork は a16z から $2.8M を調達し、月間訪問は74万件規模まで伸びています
記事一覧/開発ツール
開発ツール/2026-06-15上級

Rork で作った Expo アプリのログインを切らさない — トークン更新の競合を単一化で防ぐ

Rork が生成する Expo アプリにログインを足すと、最初は動いても本番でじわじわ「勝手にログアウトされる」報告が増えます。原因の多くはトークン更新の競合です。リフレッシュを単一化し、安全に保存し、失効を正しく扱う信頼性設計を実装視点でまとめます。

Expo156認証14トークン信頼性Rork525

プレミアム記事

ログイン機能を足したアプリを公開して一週間ほど経った頃、「気づくとログアウトしている」という問い合わせが少しずつ届き始めました。手元では再現しません。自分の端末では一度もログアウトされないのに、特定の利用者だけが繰り返し弾かれます。

原因を追ってログを並べると、同じ瞬間に複数のリクエストがトークン更新を始め、片方が古いトークンを無効化し、もう片方が無効化されたトークンで保存処理を上書きしていました。トークン更新の競合です。Rork が生成する Expo アプリにそのままログインを足すと、この競合は表に出にくく、本番運用で利用者が増えてから牙をむきます。今回は、ログインを切らさないための信頼性設計を共有します。

なぜ手元では再現しないのか

開発中は、たいてい操作が直列です。画面を開き、ボタンを押し、結果を見る。リクエストが重なりません。ところが本番運用では、アプリ復帰時に複数の画面が同時にデータを取りに行き、全部のリクエストがほぼ同時に 401 を受け取ります。

ここで各リクエストが個別にトークン更新を始めると、更新が同時多発します。多くの認証基盤はリフレッシュトークンを一度使うと回転させて古いものを失効させるため、二番目以降の更新は「すでに使われたトークン」を握ったまま失敗します。結果として、せっかく取れた新しいトークンが、遅れて届いた失敗応答に上書きされ、利用者は突然ログアウトされます。

手元で再現しないのは、リクエストを重ねていないからです。この種のバグは利用者の数とネットワークの遅さに比例して増えるので、公開直後ほど見つけにくいのが厄介な点です。私自身、App Store の審査は問題なく通ったのに、公開後一週間で問い合わせが増えていく、という経験をしました。

更新を単一化する single-flight

対処の核心は、同時に何本のリクエストが 401 を受けても、トークン更新は一度しか走らせないことです。最初に更新を始めた一本だけが実際に通信し、残りはその結果を待って共有します。これを single-flight と呼びます。

let refreshPromise = null;
 
async function getFreshToken(refreshFn) {
  // すでに更新中なら、その Promise を共有して待つ
  if (refreshPromise) return refreshPromise;
 
  refreshPromise = (async () => {
    try {
      const tokens = await refreshFn();   // 実際の更新通信は一回だけ
      await saveTokens(tokens);
      return tokens.accessToken;
    } finally {
      refreshPromise = null;              // 完了したら必ず解放する
    }
  })();
 
  return refreshPromise;
}

肝は finally での解放です。ここを忘れると、一度更新に失敗したあと refreshPromise が残り続け、以降の更新が永久に古い結果を返す状態に陥ります。私はこの解放を入れ忘れて、「一度ログアウトすると二度とログインできない」という、もっと悪い不具合を作った経験があります。

401 を受けたら一度だけ再試行する

single-flight で新しいトークンを得たら、失敗したリクエストをそのトークンで一度だけやり直します。やり直しを無制限にすると、本当に失効している場合に無限ループに入るため、再試行は一回に限ります。

async function fetchWithAuth(url, options, deps) {
  let token = await deps.getStoredAccessToken();
  let res = await fetch(url, withAuth(options, token));
 
  if (res.status === 401) {
    token = await getFreshToken(deps.refreshFn);   // ここは single-flight
    if (!token) return res;                         // 更新自体が失敗 = ログアウト確定
    res = await fetch(url, withAuth(options, token)); // 一度だけ再試行
  }
  return res;
}

更新自体が失敗したときに無理に粘らないのも大事です。リフレッシュトークンが本当に失効しているなら、何度試しても通りません。その場合は潔くログイン画面に戻すほうが、利用者にとっても挙動が読めます。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
複数リクエストが同時に 401 を受けて更新が二重発火する競合を、単一化(single-flight)で防ぐ実装が持ち帰れます
リフレッシュトークンの回転と SecureStore 保存で、失効と漏洩リスクをどう両立させるかの判断基準がわかります
端末の時計ずれ・オフライン復帰で起きる「勝手にログアウト」を、再試行と猶予の設計で約 80% 減らした手順がわかります
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-04-27
EXPO_PUBLIC_RORK_AUTH_URL を空欄のまま本番に出さないために — Rork 生成アプリの認証URL設定
Rorkで生成したアプリのコードに現れる EXPO_PUBLIC_RORK_AUTH_URL に何を入れるべきか、開発・EAS Build・本番でどう切り替えるかを実例付きで整理しました。空欄のまま審査に出す事故を防ぎます。
開発ツール2026-04-24
Rork アプリに Sign in with Apple を組み込む手順 — 審査で求められる実装とよくある詰まりどころ
Rork で作ったアプリに Apple ID サインインを後付けする手順をまとめました。Google/Facebook ログインを入れた途端に審査で指摘される Guideline 4.8 の要件、expo-apple-authentication の落とし穴、Android・Web での代替フローまで、実装に詰まった時に参照できる形で整理しています。
開発ツール2026-08-02
2文字の検索語が静かに0件になる — Expo アプリの日本語検索インデックスを実測で選び直す
SQLite FTS5 の trigram トークナイザは2文字以下の日本語クエリを例外なしで0件にします。2万件のカタログで線形走査・bigram 転置索引・FTS5 を実測し、索引を足すべき閾値と起動コストの実数を出した記録です。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →