認証フロー詳細 Authentication flow details

Parky モバイルアプリにおける全認証シナリオの仕様を定義します。 メール+パスワードによるサインイン・サインアップ、OTP 検証、パスワードリセット、 OAuth(Google / Apple / Facebook)、重複メール検出、退会済みユーザーの再登録、 ブロックユーザーへの対応を網羅します。 実装時の認証設計の一次情報源として参照してください。

Defines the spec for every authentication scenario in the Parky mobile app: email + password sign-in and sign-up, OTP verification, password reset, OAuth (Google / Apple / Facebook), duplicate email detection, re-registration for withdrawn users, and handling of blocked accounts. Use this page as the primary reference when implementing authentication.

2026-04-23 SDUI L3 化: 2026-04-23 SDUI L3 cutover: 書き込み系(OTP request / OTP verify / sign-up / sign-out)は 全て BFF Action 経由 に統一されています。 Flutter から直接 supabase.auth.signInWithPassword / supabase.auth.signUp を呼ぶ運用は廃止し、以下を使うこと。
  • POST /v1/mobile/actions/auth/request-otp — メール OTP 送信(preflight は BFF 内部で評価)
  • POST /v1/mobile/actions/auth/verify-otp — OTP 検証+ accessToken / refreshToken を ActionEnvelope で受領
  • POST /v1/mobile/actions/auth/sign-up — email + password 新規登録(preflight・app_users upsert・price arm 自動割当を一括)
  • POST /v1/mobile/actions/auth/sign-out — サインアウト
  • GET /v1/mobile/views/auth-config — ログイン画面の初期データ(providers / 文言 / consent text、認証不要)
Supabase Auth SDK は session 永続化と JWT refresh のみ に使う。POST /v1/auth/preflight は public 用 endpoint で mobile からは叩かない。
All write operations (OTP request / verify, sign-up, sign-out) now run through BFF actions. Stop calling supabase.auth.signInWithPassword / supabase.auth.signUp directly from Flutter; use the BFF endpoints below.
  • POST /v1/mobile/actions/auth/request-otp
  • POST /v1/mobile/actions/auth/verify-otp (returns accessToken / refreshToken inside the ActionEnvelope)
  • POST /v1/mobile/actions/auth/sign-up (preflight + app_users upsert + price-arm assignment in one shot)
  • POST /v1/mobile/actions/auth/sign-out
  • GET /v1/mobile/views/auth-config
Use the Supabase Flutter SDK only for session persistence and JWT refresh. POST /v1/auth/preflight is the public/web endpoint — the mobile app never calls it directly.

1. サインイン(email + password) 1. Sign-in (email + password)

既存ユーザーがメールアドレスとパスワードでサインインします。 ロックアウト状態の場合はロック解除時刻を表示します。

Existing users sign in with their email address and password. If the account is locked, the unlock time is displayed.

フロー詳細 Flow details

  1. ユーザーがメール・パスワードを入力して「ログイン」ボタンをタップ
  2. User enters email and password, then taps the "Sign in" button
  3. supabase.auth.signInWithPassword({ email, password }) を呼び出す
  4. Call supabase.auth.signInWithPassword({ email, password })
  5. 成功:セッション取得後 /main へ遷移する
  6. Success: retrieve session and navigate to /main
  7. 失敗 (invalid_credentials):「メールアドレスまたはパスワードが正しくありません」を表示
  8. Failure (invalid_credentials): show "Email address or password is incorrect"
  9. アカウントロック (parky.account.locked):ペイロードの locked_until を使用して「〇月〇日 〇時まで一時停止中です」を表示
  10. Account locked (parky.account.locked): use locked_until from the payload to show "Temporarily locked until [date/time]"
  11. ブロック (parky.account.blocked):「このアカウントは利用停止されています。サポートにお問い合わせください」を表示
  12. Blocked (parky.account.blocked): show "This account has been suspended. Please contact support"
  13. 連続ログイン失敗 5 回でアカウントを一時ロック(parky.account.locked を返す)
  14. 5 consecutive failures temporarily lock the account (returns parky.account.locked)

2. サインアップ 2. Sign-up

新規ユーザーがメールアドレスとパスワードでアカウントを作成します。 登録前に preflight API でメール状態を確認し、問題なければ Supabase にサインアップします。 サインアップ完了後は OTP 画面へ遷移し、メール確認を行います。

A new user creates an account with an email address and password. Before registration, the preflight API checks the email status; if clear, the app signs the user up via Supabase. After sign-up the app navigates to the OTP screen for email verification.

フロー詳細 Flow details

  1. ユーザーがメール・パスワード・表示名を入力して「登録」ボタンをタップ
  2. User enters email, password, and display name, then taps "Register"
  3. POST /v1/auth/preflight { email } を呼び出しメール状態を確認する
  4. Call POST /v1/auth/preflight { email } to check email status
  5. preflight が available を返した場合のみ次ステップへ進む(その他の値はエラー表示)
  6. Proceed to the next step only when preflight returns available (any other value shows an error)
  7. supabase.auth.signUp({ email, password, options: { data: { full_name } } }) を呼び出す
  8. Call supabase.auth.signUp({ email, password, options: { data: { full_name } } })
  9. 成功 → /otp?email=xxx へ遷移する(メールアドレスをクエリで渡す)
  10. Success → navigate to /otp?email=xxx (pass the email address as a query parameter)

3. OTP 検証 3. OTP verification

サインアップ後に Supabase が送信したメール内の 6 桁コードを入力して本人確認を行います。

After sign-up, the user enters the 6-digit code from the email sent by Supabase to confirm their identity.

仕様 Spec

項目Item Value
コード桁数Code length 6 digits
有効期限Expiry 5 minutes
再送クールダウンResend cooldown 60 seconds
試行制限Attempt limit 5 回失敗でロック(Supabase デフォルト)Locked after 5 failures (Supabase default)
API 呼び出しAPI call supabase.auth.verifyOTP({ type: "signup", token, email })
成功後の遷移On success パーミッション要求画面 → /mainPermission request screen → /main

4. パスワードリセット(Forgot Password) 4. Forgot password / Password reset

ユーザーがパスワードを忘れた場合、メールでリセットリンクを受け取り、 ディープリンク経由でアプリ内のパスワード変更画面へ遷移します。

When a user forgets their password, they receive a reset link by email and are taken to the in-app password change screen via a deep link.

フロー詳細 Flow details

  1. サインイン画面の「パスワードを忘れた方」リンクをタップ
  2. Tap "Forgot password?" on the sign-in screen
  3. メールアドレス入力画面へ遷移
  4. Navigate to the email input screen
  5. supabase.auth.resetPasswordForEmail(email, { redirectTo: "parky://reset-password" }) を呼び出す
  6. Call supabase.auth.resetPasswordForEmail(email, { redirectTo: "parky://reset-password" })
  7. Supabase がリセットメールを送信する(存在しないメールでも 200 を返す:ユーザー列挙を防ぐ)
  8. Supabase sends the reset email (always returns 200, even for unknown emails — prevents user enumeration)
  9. 「リセット用のメールを送信しました。受信トレイをご確認ください」を表示
  10. Show "A reset email has been sent. Please check your inbox"
  11. ユーザーがメール内リンクをタップ → ディープリンク parky://reset-password?token=xxx でアプリ起動
  12. User taps the link in the email → app opens via deep link parky://reset-password?token=xxx
  13. アプリが /reset-password 画面を表示し、新パスワード入力を求める
  14. App shows the /reset-password screen and prompts for a new password
  15. supabase.auth.updateUser({ password: newPassword }) を呼び出す
  16. Call supabase.auth.updateUser({ password: newPassword })
  17. 成功 → 「パスワードを変更しました」トースト → /main へ遷移
  18. Success → "Password updated" toast → navigate to /main

5. OAuth(Google / Apple / Facebook) 5. OAuth (Google / Apple / Facebook)

ソーシャルアカウントでのサインイン・サインアップを提供します。 iOS の Apple サインインはネイティブ実装を使用します。 重要:Supabase Auth Hook before_user_created により、 preflight で未登録と判定されたメールアドレスはアカウントの自動作成を拒否します。

Provides sign-in and sign-up via social accounts. Apple Sign-In on iOS uses the native implementation. Important: the Supabase Auth Hook before_user_created rejects automatic account creation for email addresses that are not pre-registered via preflight.

プロバイダー別実装 Per-provider implementation

プロバイダーProvider API 呼び出しAPI call 備考Notes
Apple (iOS) supabase.auth.signInWithIdToken({ provider: "apple", idToken, nonce }) iOS ネイティブ ASAuthorizationAppleIDRequest からトークン取得Token obtained from iOS native ASAuthorizationAppleIDRequest
Google supabase.auth.signInWithOAuth({ provider: "google" }) Google Sign-In SDK 経由で認証Authenticated via Google Sign-In SDK
Facebook supabase.auth.signInWithOAuth({ provider: "facebook" }) Facebook Login SDK 経由で認証Authenticated via Facebook Login SDK

未登録メールの扱い(自動登録拒否) Unregistered email handling (auto-registration rejected)

6. 重複メール検出 6. Duplicate email detection

POST /v1/auth/preflight はサインアップ前のメール状態チェックに使用します。 既に登録済みのメールアドレスを検出した場合、登録方法を明示してユーザーを適切な画面へ誘導します。

POST /v1/auth/preflight is used to check email status before sign-up. When a duplicate email is detected, the registration method is shown explicitly and the user is directed to the appropriate screen.

preflight レスポンス値と UI 対応 Preflight response values and UI behavior

status 値status value 意味Meaning UI 表示UI message 誘導先Redirect
available 登録可能Available for registration (エラーなし、そのまま続行)(No error; proceed)
exists_with_password メール+パスワードで登録済みRegistered with email + password 「このメールアドレスはすでにパスワードで登録されています。ログインしてください」"This email is already registered with a password. Please sign in." /sign-in
exists_with_oauth:google Google で登録済みRegistered via Google 「このメールアドレスは Google で登録されています。Google でログインしてください」"This email is registered via Google. Please sign in with Google." /sign-in
exists_with_oauth:apple Apple で登録済みRegistered via Apple 「このメールアドレスは Apple で登録されています。Apple でログインしてください」"This email is registered via Apple. Please sign in with Apple." /sign-in
exists_with_oauth:facebook Facebook で登録済みRegistered via Facebook 「このメールアドレスは Facebook で登録されています。Facebook でログインしてください」"This email is registered via Facebook. Please sign in with Facebook." /sign-in
withdrawn_rejoinable 退会済み(再登録可)Withdrawn (re-registration allowed) (エラーなし、通常サインアップフローへ)(No error; proceed with normal sign-up flow)
blocked ブロック済みBlocked 「このメールアドレスは利用停止されています。サポートにお問い合わせください」"This email address has been suspended. Please contact support." サポートリンクSupport link

7. 退会済みユーザーの再登録 7. Re-registration for withdrawn users

退会済みユーザーが同じメールアドレスで再登録する場合、preflight は withdrawn_rejoinable を返します。 クライアントはこれをエラーとして扱わず、通常のサインアップフローへ進みます。 バックエンドは退会済みアカウントを復元せず、新規アカウントとして作成します。

When a withdrawn user re-registers with the same email, preflight returns withdrawn_rejoinable. The client does not treat this as an error and proceeds with the normal sign-up flow. The backend does not restore the withdrawn account; it creates a fresh account.

8. ブロックユーザーへの対応 8. Blocked user handling

管理者によりブロックされたユーザーは、サインイン・サインアップ・OAuth のいずれの経路でも認証が拒否されます。

Users blocked by an administrator are rejected through all authentication paths: sign-in, sign-up, and OAuth.

9. シーケンス図 9. Sequence diagrams

9.1 メールサインアップ + OTP 9.1 Email sign-up + OTP

sequenceDiagram
  participant U as ユーザー / User
  participant App as モバイルアプリ / App
  participant API as Parky API
  participant Sup as Supabase Auth

  U->>App: メール・パスワード・表示名を入力
  App->>API: POST /v1/auth/preflight { email }
  API-->>App: { status: "available" }
  App->>Sup: supabase.auth.signUp({ email, password, data: { full_name } })
  Sup-->>App: { user, session: null }
  Note over Sup: 確認メール送信 / Sends confirmation email
  App->>App: navigate("/otp?email=xxx")

  U->>App: 6桁 OTP コードを入力 / Enter 6-digit OTP
  App->>Sup: supabase.auth.verifyOTP({ type: "signup", token, email })
  Sup-->>App: { user, session }
  App->>App: navigate("/permissions")
  App->>App: navigate("/main")

9.2 メールサインイン + ロックアウト 9.2 Email sign-in + lockout

sequenceDiagram
  participant U as ユーザー / User
  participant App as モバイルアプリ / App
  participant Sup as Supabase Auth

  U->>App: メール・パスワードを入力 / Enter email + password
  App->>Sup: supabase.auth.signInWithPassword({ email, password })

  alt 認証成功 / Auth success
    Sup-->>App: { user, session }
    App->>App: navigate("/main")
  else 認証失敗 / Auth failure (invalid_credentials)
    Sup-->>App: error: invalid_credentials
    App->>U: 「メールまたはパスワードが正しくありません」
    Note over App: 5回失敗でロック / Locked after 5 failures
  else アカウントロック / Account locked
    Sup-->>App: error: parky.account.locked { locked_until }
    App->>U: 「○月○日○時まで一時停止中です」
  else アカウントブロック / Account blocked
    Sup-->>App: error: parky.account.blocked
    App->>U: 「このアカウントは利用停止されています」
  end

9.3 パスワードリセット 9.3 Forgot password / reset

sequenceDiagram
  participant U as ユーザー / User
  participant App as モバイルアプリ / App
  participant Sup as Supabase Auth
  participant Mail as メールサーバー / Mail server

  U->>App: 「パスワードを忘れた方」タップ
  App->>App: navigate("/forgot-password")
  U->>App: メールアドレスを入力 / Enter email
  App->>Sup: supabase.auth.resetPasswordForEmail(email, { redirectTo: "parky://reset-password" })
  Sup-->>App: { data: {}, error: null }
  Note over Sup,Mail: メール送信(存在しないメールでも200)
  Sup->>Mail: リセットリンクメール送信
  App->>U: 「リセット用のメールを送信しました」

  U->>Mail: メール内リンクをタップ / Tap link in email
  Mail-->>App: Deep link: parky://reset-password?token=xxx
  App->>App: navigate("/reset-password", token)
  U->>App: 新パスワードを入力 / Enter new password
  App->>Sup: supabase.auth.updateUser({ password })
  Sup-->>App: { user, session }
  App->>U: 「パスワードを変更しました」トースト
  App->>App: navigate("/main")

9.4 OAuth + preflight + 未登録時の誘導 9.4 OAuth + preflight + redirect for unregistered users

sequenceDiagram
  participant U as ユーザー / User
  participant App as モバイルアプリ / App
  participant Provider as OAuth Provider (Google / Apple / Facebook)
  participant Sup as Supabase Auth
  participant Hook as before_user_created Hook
  participant API as Parky API

  U->>App: 「Google でログイン」タップ / Tap "Sign in with Google"
  App->>Provider: OAuth 認証要求 / OAuth auth request
  Provider-->>App: idToken / OAuth token

  alt Apple (iOS native)
    App->>Sup: supabase.auth.signInWithIdToken({ provider: "apple", idToken, nonce })
  else Google / Facebook
    App->>Sup: supabase.auth.signInWithOAuth({ provider: "google" })
  end

  alt 既存ユーザー / Existing user
    Sup-->>App: { user, session }
    App->>App: navigate("/main")
  else 新規ユーザー / New user — Hook 介入
    Sup->>Hook: before_user_created イベント
    Hook->>API: POST /v1/auth/preflight { email }
    API-->>Hook: { status: "available" }
    Hook-->>Sup: 作成許可 / Allow creation
    Sup-->>App: { user, session }
    App->>App: navigate("/main")
  else 未登録 / Not registered
    Sup->>Hook: before_user_created イベント
    Hook->>API: POST /v1/auth/preflight { email }
    API-->>Hook: { status: "blocked" or other }
    Hook-->>Sup: エラー: parky.oauth.not_registered
    Sup-->>App: error: parky.oauth.not_registered
    App->>U: 「まだ登録されていません。新規登録しますか?」
    U->>App: 「登録する」タップ
    App->>App: navigate("/create-account")
  end

10. エラーコード一覧 10. Error code reference

Parky カスタムエラー Parky custom errors

エラーコードError code 発生箇所Where raised ペイロードPayload UI 表示テキスト (ja)UI message (en)
parky.oauth.not_registered before_user_created Hookbefore_user_created hook このメールアドレスはまだ登録されていません。新規登録画面へ進みますか? This email is not yet registered. Would you like to sign up?
parky.account.blocked サインイン / preflight / signUpSign-in / preflight / signUp このアカウントは利用停止されています。サポートにお問い合わせください。 This account has been suspended. Please contact support.
parky.account.locked サインイン(連続失敗後)Sign-in (after repeated failures) { locked_until: ISO8601 } 連続ログイン失敗のため一時的にロックされています。〇月〇日 〇時以降に再試行してください。 Temporarily locked due to repeated failures. Please try again after [locked_until].
parky.email.exists_with_password preflight / signUppreflight / signUp このメールアドレスはすでにパスワードで登録されています。ログインしてください。 This email is already registered with a password. Please sign in.
parky.email.exists_with_oauth preflight / signUppreflight / signUp { provider: "google" | "apple" | "facebook" } このメールアドレスは [provider] で登録されています。[provider] でログインしてください。 This email is registered via [provider]. Please sign in with [provider].

Supabase 標準エラーと日本語訳 Supabase standard errors and Japanese translations

Supabase エラーコードSupabase error code 日本語 UI テキストEnglish UI text 発生条件When raised
invalid_credentials メールアドレスまたはパスワードが正しくありません Email address or password is incorrect パスワード不一致Password mismatch
email_not_confirmed メールアドレスが確認されていません。受信トレイを確認してください Email address not confirmed. Please check your inbox OTP 未完了でサインイン試行Sign-in attempted before OTP verified
over_email_send_rate_limit メール送信の上限に達しました。しばらくしてから再試行してください Email send rate limit reached. Please try again later OTP 再送・リセットメールの連続送信Rapid OTP resend or repeated reset emails
otp_expired コードの有効期限が切れました。再送してください The code has expired. Please request a new one OTP 入力が 5 分を超過OTP entered after 5-minute window
otp_disabled この操作は現在使用できません。サポートにお問い合わせください This operation is currently unavailable. Please contact support OTP が無効化されているOTP disabled on the project
user_already_exists このメールアドレスはすでに登録されています This email address is already registered preflight をバイパスしたサインアップ(防御)Sign-up bypassed preflight (defensive)
weak_password パスワードが短すぎます。8文字以上で設定してください Password is too short. Please use at least 8 characters パスワード強度不足Password strength insufficient

11. 関連 API 一覧 11. Related API reference

Parky 独自エンドポイント Parky custom endpoints

エンドポイントEndpoint メソッドMethod 認証Auth リクエストRequest レスポンスResponse 用途Purpose
/v1/auth/preflight POST 不要None { email: string } { status: string, provider?: string } サインアップ前のメール状態確認・重複検出Pre-signup email status check and duplicate detection
/v1/auth/config GET 不要None { oauth_providers: string[], password_min_length: number } クライアントが利用可能な OAuth プロバイダー・パスワードポリシーを取得Retrieve available OAuth providers and password policy for the client

Supabase Auth SDK メソッド Supabase Auth SDK methods

メソッドMethod シナリオScenario 主なパラメータKey parameters
supabase.auth.signInWithPassword メールサインインEmail sign-in { email, password }
supabase.auth.signUp メールサインアップEmail sign-up { email, password, options: { data: { full_name } } }
supabase.auth.verifyOTP OTP 検証OTP verification { type: "signup", token, email }
supabase.auth.resetPasswordForEmail パスワードリセットメール送信Send password reset email (email, { redirectTo: "parky://reset-password" })
supabase.auth.updateUser 新パスワード確定Confirm new password { password }
supabase.auth.signInWithOAuth OAuth サインイン(Google / Facebook)OAuth sign-in (Google / Facebook) { provider: "google" | "facebook" }
supabase.auth.signInWithIdToken Apple ネイティブサインイン(iOS)Apple native sign-in (iOS) { provider: "apple", idToken, nonce }
supabase.auth.getSession 起動時セッション確認Startup session check
supabase.auth.signOut サインアウトSign-out

12. 関連仕様書 12. Related documents

仕様書Document 内容Content
オンボーディングフローOnboarding flow スプラッシュ起動判定・3ページスライド・「はじめる」遷移Splash routing, 3-page carousel, "Get started" transition
画面カタログScreen catalog 全 32 画面の一覧・ルート・API 対応All 32 screens with routes and API mapping
利用フロー・状態遷移User flows & state transitions エンドツーエンドシナリオ・エンティティステータス遷移End-to-end scenarios and entity state transitions
セキュリティ強化Security hardening ロックアウト・レート制限・JWT 検証の詳細Lockout, rate limiting, and JWT validation details
API 仕様API spec 全エンドポイントのリクエスト・レスポンス仕様Request/response spec for all endpoints