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
- Generate Redirectly links in your Supabase auth hooks
- Include verification token in deep link parameters
- User clicks link, installs app if needed, and is auto-verified
- Seamless onboarding experience
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.
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(),
)
}
}
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')
}
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.
Next steps
Combine Supabase auth with Redirectly deferred deep linking: