認証フロー詳細 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.
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_usersupsert・price arm 自動割当を一括)POST /v1/mobile/actions/auth/sign-out— サインアウトGET /v1/mobile/views/auth-config— ログイン画面の初期データ(providers / 文言 / consent text、認証不要)
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-otpPOST /v1/mobile/actions/auth/verify-otp(returns accessToken / refreshToken inside the ActionEnvelope)POST /v1/mobile/actions/auth/sign-up(preflight +app_usersupsert + price-arm assignment in one shot)POST /v1/mobile/actions/auth/sign-outGET /v1/mobile/views/auth-config
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
- ユーザーがメール・パスワードを入力して「ログイン」ボタンをタップ
- User enters email and password, then taps the "Sign in" button
supabase.auth.signInWithPassword({ email, password })を呼び出す- Call
supabase.auth.signInWithPassword({ email, password }) - 成功:セッション取得後
/mainへ遷移する - Success: retrieve session and navigate to
/main - 失敗 (
invalid_credentials):「メールアドレスまたはパスワードが正しくありません」を表示 - Failure (
invalid_credentials): show "Email address or password is incorrect" - アカウントロック (
parky.account.locked):ペイロードのlocked_untilを使用して「〇月〇日 〇時まで一時停止中です」を表示 - Account locked (
parky.account.locked): uselocked_untilfrom the payload to show "Temporarily locked until [date/time]" - ブロック (
parky.account.blocked):「このアカウントは利用停止されています。サポートにお問い合わせください」を表示 - Blocked (
parky.account.blocked): show "This account has been suspended. Please contact support" - 連続ログイン失敗 5 回でアカウントを一時ロック(
parky.account.lockedを返す) - 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
- ユーザーがメール・パスワード・表示名を入力して「登録」ボタンをタップ
- User enters email, password, and display name, then taps "Register"
POST /v1/auth/preflight { email }を呼び出しメール状態を確認する- Call
POST /v1/auth/preflight { email }to check email status - preflight が
availableを返した場合のみ次ステップへ進む(その他の値はエラー表示) - Proceed to the next step only when preflight returns
available(any other value shows an error) supabase.auth.signUp({ email, password, options: { data: { full_name } } })を呼び出す- Call
supabase.auth.signUp({ email, password, options: { data: { full_name } } }) - 成功 →
/otp?email=xxxへ遷移する(メールアドレスをクエリで渡す) - 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 |
- コード入力は自動フォーカス+数字キーボード。6 桁入力完了で自動サブミット
- Code input auto-focuses with a numeric keyboard; auto-submits when all 6 digits are entered
- 再送ボタンはクールダウンタイマー(カウントダウン表示)付き
- The resend button shows a countdown timer during the cooldown period
- 有効期限切れ (
otp_expired) 時は「コードの有効期限が切れました。再送してください」を表示 - On expiry (
otp_expired): show "The code has expired. Please request a new one"
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
- サインイン画面の「パスワードを忘れた方」リンクをタップ
- Tap "Forgot password?" on the sign-in screen
- メールアドレス入力画面へ遷移
- Navigate to the email input screen
supabase.auth.resetPasswordForEmail(email, { redirectTo: "parky://reset-password" })を呼び出す- Call
supabase.auth.resetPasswordForEmail(email, { redirectTo: "parky://reset-password" }) - Supabase がリセットメールを送信する(存在しないメールでも 200 を返す:ユーザー列挙を防ぐ)
- Supabase sends the reset email (always returns 200, even for unknown emails — prevents user enumeration)
- 「リセット用のメールを送信しました。受信トレイをご確認ください」を表示
- Show "A reset email has been sent. Please check your inbox"
- ユーザーがメール内リンクをタップ → ディープリンク
parky://reset-password?token=xxxでアプリ起動 - User taps the link in the email → app opens via deep link
parky://reset-password?token=xxx - アプリが
/reset-password画面を表示し、新パスワード入力を求める - App shows the
/reset-passwordscreen and prompts for a new password supabase.auth.updateUser({ password: newPassword })を呼び出す- Call
supabase.auth.updateUser({ password: newPassword }) - 成功 → 「パスワードを変更しました」トースト →
/mainへ遷移 - 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 |
supabase.auth.signInWithOAuth({ provider: "google" }) |
Google Sign-In SDK 経由で認証Authenticated via Google Sign-In SDK | |
supabase.auth.signInWithOAuth({ provider: "facebook" }) |
Facebook Login SDK 経由で認証Authenticated via Facebook Login SDK |
未登録メールの扱い(自動登録拒否) Unregistered email handling (auto-registration rejected)
- Supabase Auth Hook
before_user_createdがPOST /v1/auth/preflightを呼び出す - The Supabase Auth Hook
before_user_createdinternally callsPOST /v1/auth/preflight - preflight が
available以外を返した場合(既存アカウントや blocked 等)、Hook がユーザー作成を中断してエラーコードを返す - If preflight returns anything other than
available(existing account, blocked, etc.), the hook aborts user creation and returns an error code - クライアントはエラーコード
parky.oauth.not_registeredを受け取った場合、 「このメールアドレスはまだ登録されていません。新規登録画面へ進みますか?」ダイアログを表示して/create-accountへ誘導する(自動サインアップは行わない) - When the client receives error code
parky.oauth.not_registered, it shows a dialog "This email address is not yet registered. Would you like to sign up?" and directs the user to/create-account(no automatic sign-up)
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.
- 退会済みの
app_usersレコードはwithdrawnステータスのまま保持(統計データ保全) - The
app_usersrecord for the withdrawn account is kept aswithdrawn(for statistical data) - 新しい Supabase Auth ユーザー + 新しい
app_usersレコードが作成される - A new Supabase Auth user and a new
app_usersrecord are created - お気に入り・駐車履歴は引き継がない(個人情報匿名化済みのため)
- Favorites and parking history are not migrated (personal info was anonymized at withdrawal)
8. ブロックユーザーへの対応 8. Blocked user handling
管理者によりブロックされたユーザーは、サインイン・サインアップ・OAuth のいずれの経路でも認証が拒否されます。
Users blocked by an administrator are rejected through all authentication paths: sign-in, sign-up, and OAuth.
- サインイン試行時:
parky.account.blockedエラーを返す - On sign-in attempt: return
parky.account.blockederror - サインアップ試行時:preflight が
blockedを返し、登録を拒否する - On sign-up attempt: preflight returns
blockedand registration is rejected - OAuth 試行時:
before_user_createdHook がparky.oauth.not_registeredを返す - On OAuth attempt: the
before_user_createdhook returnsparky.oauth.not_registered - UI:「このアカウントは利用停止されています。サポートへのリンク」を表示(サポートメール: support@parky.co.jp)
- UI: show "This account has been suspended." with a link to support (email: support@parky.co.jp)
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 |