Supabase + Flutter Deep Linking

Supabase Deep Linking with Redirectly

Complete guide to implementing email verification and magic link flows in Flutter. Learn how to handle authentication deep links and verify users when they launch your app.

Auth Integration

Supabase + Redirectly for auth flows

Supabase provides excellent authentication infrastructure, and Redirectly enables deferred deep linking for authentication flows. When users click email verification or magic link emails, Redirectly ensures they reach the right screen even if your app isn't installed yet.

The challenge

  • User receives email verification link from Supabase
  • If they haven't installed your app yet, the link doesn't work
  • Manual copy-paste of tokens is frustrating and error-prone
  • Users abandon signup or email verification flows

The solution

  1. Generate Redirectly links in your Supabase auth hooks
  2. Include verification token in deep link parameters
  3. User clicks link, installs app if needed, and is auto-verified
  4. Seamless onboarding experience
Implementation

Email verification flow implementation

Set up email verification using Supabase and Redirectly together. This example shows how to generate verification links that work before app installation.

// lib/services/supabase_auth_service.dart import 'package:supabase_flutter/supabase_flutter.dart' import 'package:redirectly/redirectly.dart' import 'package:http/http.dart' as http class SupabaseAuthService { final supabase = Supabase.instance.client Future<void> signUpWithEmail( String email, String password, ) async { try { // Sign up with Supabase await supabase.auth.signUp( email: email, password: password, ) // Generate Redirectly deep link for verification final deepLink = await _generateVerificationLink(email) // Send custom email with Redirectly link // (Or configure Supabase auth emails to use your domain) await _sendVerificationEmail(email, deepLink) } catch (e) { print('Sign up error: $e') } } Future<String> _generateVerificationLink(String email) async { final session = supabase.auth.currentSession final token = session?.user.id ?? '' // Create Redirectly deep link with verification token final redirectlyUrl = Uri.https( 'api.redirectly.app', '/link/create', { 'apiKey': 'your_api_key', 'path': 'verify-email', 'query': 'token=$token&email=$email', 'subdomain': 'myapp', }, ) final response = await http.post( redirectlyUrl, headers: {'Content-Type': 'application/json'}, ) if (response.statusCode == 200) { final json = jsonDecode(response.body) return json['link'] as String } throw Exception('Failed to create link') } Future<void> _sendVerificationEmail( String email, String deepLink, ) async { // Use your email service to send verification email // Include the Redirectly link print('Send verification email to $email with link: $deepLink') } }

The key insight: include the verification token as a query parameter in the Redirectly deep link. When your app launches, retrieve this token and confirm it with Supabase.

Handle Deep Links

Verify emails when app launches

In your app's main widget, listen for deep link events and handle email verification automatically.

// lib/main.dart import 'package:flutter/material.dart' import 'package:redirectly/redirectly.dart' import 'services/supabase_auth_service.dart' void main() async { WidgetsFlutterBinding.ensureInitialized() await Redirectly.initialize( apiKey: 'your_api_key', ) runApp(const MyApp()) } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key) @override State<MyApp> createState() => _MyAppState() } class _MyAppState extends State<MyApp> { final authService = SupabaseAuthService() @override void initState() { super.initState() _setupDeepLinkListener() } void _setupDeepLinkListener() { // Listen to deep links from Redirectly Redirectly.onDeepLink.listen((link) { print('Deep link received: ${link.path}') print('Query params: ${link.params}') // Handle email verification flow if (link.path == 'verify-email') { final token = link.params['token'] as String? final email = link.params['email'] as String? if (token != null && email != null) { _handleEmailVerification(token, email) } } }) } Future<void> _handleEmailVerification( String token, String email, ) async { try { // Verify with Supabase using the token final response = await authService .supabase .auth .verifyOTP( email: email, type: OtpType.email, token: token, ) if (response.session != null) { // User is verified, navigate to main app Navigator.of(context).pushReplacementNamed('/home') ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Email verified!')), ) } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Verification failed: $e')), ) } } @override Widget build(BuildContext context) { return MaterialApp( title: 'MyApp', home: const AuthScreen(), ) } }
Magic Links

Passwordless auth with magic links

Supabase also supports passwordless sign-in with magic links. Use the same Redirectly integration for an even smoother experience.

// Magic link sign-in with Redirectly deep linking Future<void> signInWithMagicLink(String email) async { try { // Generate Redirectly deep link for magic link flow final deepLink = await _generateMagicLinkDeepLink(email) // Sign in with Supabase magic link await supabase.auth.signInWithOtp( email: email, emailRedirectTo: deepLink, // Use Redirectly link ) ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Check your email for the Redirectly deep link', ), ), ) } catch (e) { print('Magic link error: $e') } } // Redirectly link for magic link flow Future<String> _generateMagicLinkDeepLink(String email) async { final response = await http.post( Uri.https('api.redirectly.app', '/link/create'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'apiKey': 'your_api_key', 'path': 'magic-link-signin', 'query': 'email=$email', 'subdomain': 'myapp', }), ) if (response.statusCode == 200) { return jsonDecode(response.body)['link'] } throw Exception('Failed to create magic link') }
Best Practices

Key considerations for secure auth flows

Token expiration

Set reasonable expiration times on verification tokens. Supabase automatically expires OTP tokens after 15 minutes by default. Consider the time it takes for app installation.

Secure link generation

Always generate Redirectly links server-side using your API key. Never expose tokens in client-side code. Use HTTPS for all link generation and token transmission.

Redirect URL validation

Validate that deep link parameters match your expected schema before processing authentication. Prevent malicious actors from injecting false verification data.

Fallback handling

Provide a fallback mechanism for users who don't have your app installed. Include a web-based verification page as a backup option.

Try Redirectly Free

Get a free API key, subdomain, and 10k monthly links.