Skip to main content

supabase-flutter-auth-setup

Sets up Supabase auth flows in a Flutter app that leave the app and come back through a link: OAuth sign-in (signInWithOAuth), magic links, email confirmation and password reset. Covers the redirect URL, the dashboard allow list, the Android and iOS deep link configuration, and handling the result with onAuthStateChange. Use when adding one of these flows, or when a sign-in opens the browser but never returns to the app.

来源信息

仓库
supabase/supabase-flutter
最近来源活动
2026年10月26日 14:07
检测到的 SKILL.md 语言
英语
星标
1,040
Forks
313

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
supabase-flutter-auth-setup
description
Sets up Supabase auth flows in a Flutter app that leave the app and come back through a link: OAuth sign-in (signInWithOAuth), magic links, email confirmation and password reset. Covers the redirect URL, the dashboard allow list, the Android and iOS deep link configuration, and handling the result with onAuthStateChange. Use when adding one of these flows, or when a sign-in opens the browser but never returns to the app.
# Auth flows that return through a deep link OAuth sign-in, magic links, email confirmation and password reset all end in the browser, which has to hand a link back to the app. `supabase_flutter` listens for that link through `app_links`, exchanges it for a session, and emits the result on `onAuthStateChange`. The app has to own a URL scheme, and the Supabase project has to allow it. Native sign-in with `signInWithIdToken` (Google, Apple and Facebook SDKs) and email or phone codes verified with `verifyOTP` do not leave the app and need none of this. ## Rules - Pick one redirect URL of the form `<scheme>://<host>`, with a scheme unique to the app, usually the reverse domain: `com.example.myapp://login-callback`. - Pass it as `redirectTo` or `emailRedirectTo` on every flow, except on web. - Add it to the redirect URL allow list of the project, or the server falls back to the Site URL and the app never opens. - Register the scheme on every platform the app ships to, and turn off Flutter's own deep link handling there, so `app_links` receives the link. - React to the result in `onAuthStateChange`, not after the call that opened the browser: the call returns before the user finishes in the browser. - Keep the default pkce flow. ## 1. Allow the redirect URL - Hosted project: Dashboard, Authentication, URL Configuration, Redirect URLs. Add `com.example.myapp://login-callback`. - Local stack: in `supabase/config.toml`: ```toml [auth] additional_redirect_urls = ["com.example.myapp://login-callback"] ``` For web, add the origin the app is served from, for example `http://localhost:3000/**` during development. ## 2. Register the scheme Android, in `android/app/src/main/AndroidManifest.xml`, inside the `<activity>` of `.MainActivity`: ```xml <meta-data android:name="flutter_deeplinking_enabled" android:value="false" /> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="com.example.myapp" android:host="login-callback" /> </intent-filter> ``` iOS, in `ios/Runner/Info.plist`, inside the top-level `<dict>`, and the same in `macos/Runner/Info.plist` for macOS: ```xml <key>FlutterDeepLinkingEnabled</key> <false/> <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLSchemes</key> <array> <string>com.example.myapp</string> </array> </dict> </array> ``` If the app already uses Flutter deep linking for its router, keep it and let the router ignore the auth callback path instead of disabling it. Windows and Linux need the scheme registered with the system, as described in the [`app_links` documentation](https://pub.dev/packages/app_links). Web needs no registration. ## 3. Start the flow ```dart file import 'package:flutter/foundation.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; const redirectUrl = 'com.example.myapp://login-callback'; Future<void> startAuthFlows(SupabaseClient supabase) async { await supabase.auth.signInWithOAuth( OAuthProvider.github, redirectTo: kIsWeb ? null : redirectUrl, ); await supabase.auth.signInWithOtp( email: 'user@example.com', emailRedirectTo: kIsWeb ? null : redirectUrl, ); await supabase.auth.resetPasswordForEmail( 'user@example.com', redirectTo: kIsWeb ? null : redirectUrl, ); } ``` `signUp` takes `emailRedirectTo` for the confirmation email in the same way. On web, `null` returns to the current page, which `supabase_flutter` picks the session up from. ## 4. Handle the result Listen once, early, for example in the root widget's `initState`: ```dart final subscription = supabase.auth.onAuthStateChange.listen( (state) { switch (state) { case AuthInitialSession(:final session): // The session restored at startup, or null. print(session == null ? 'Show sign in' : 'Show home'); case AuthSignedIn(): // OAuth, magic link and email confirmation end here. print('Show home'); case AuthPasswordRecovery(): // A password reset link was opened: ask for the new password. print('Show new password form'); case AuthSignedOut(): print('Show sign in'); case _: } }, // A failed exchange, such as an expired link, or a token refresh that // failed while offline. onError: (Object error) => print('Auth error: $error'), ); ``` Finish a password reset with the new password: ```dart await supabase.auth.updateUser(UserAttributes(password: 'a-new-password')); ``` A failed exchange, for example an expired link, arrives as an `AuthException` error on the same stream, and so does a token refresh that failed while the device was offline. Always pass `onError`: without it these errors are unhandled and reach the zone's error handler. ## Sign out `signOut()` removes the session from the device and emits `AuthSignedOut` as soon as it is called, so it works offline and the app can navigate to the sign in screen without awaiting it. The returned future waits up to 10 seconds for the server to revoke the session, so await it before disposing the client. It does not throw: a failed revoke is logged, since the session is already gone from the device and cannot be retried. To sign out every device, use `signOut(scope: SignOutScope.global)`. It asks the server first and throws an `AuthException` when that fails, keeping the session so the call can be retried. ## When the app does not open, or opens without a session | Symptom | Cause and fix | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | The browser lands on the Site URL | The redirect URL is missing from the allow list, or differs from `redirectTo` in scheme, host or path. | | The browser shows the link but the app does not open | The scheme is not registered on that platform, or the intent filter is outside `.MainActivity`. | | The app opens but no `AuthSignedIn` arrives | Flutter deep linking is still enabled and consumed the link, or the listener was attached after the link came in. | | The exchange fails with a missing code verifier | The flow was started on another device or browser than it finished on. Start and finish on the same device. | | A link from an email works once, then fails | Email links are single use and expire. Request a new one. |
在 GitHub 查看