vestibule_auth 0.0.1-beta.1 copy "vestibule_auth: ^0.0.1-beta.1" to clipboard
vestibule_auth: ^0.0.1-beta.1 copied to clipboard

Multi-tenant OTP authentication client for Vestibule

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:vestibule_auth/client.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Vestibule Auth Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const AuthDemoPage(),
    );
  }
}

class AuthDemoPage extends StatefulWidget {
  const AuthDemoPage({super.key});

  @override
  State<AuthDemoPage> createState() => _AuthDemoPageState();
}

class _AuthDemoPageState extends State<AuthDemoPage> {
  final _client = VestibuleClient(
    serverUrl: 'https://your-vestibule-server.com',
    tenantId: 'your-tenant-id',
    autoSaveTokens: true, // Automatically save tokens after authentication
  );

  final _emailController = TextEditingController();
  final _phoneController = TextEditingController();
  final _codeController = TextEditingController();

  String? _verifyToken;
  String? _jwt;
  String _status = 'Ready';
  bool _loading = false;

  @override
  void dispose() {
    _emailController.dispose();
    _phoneController.dispose();
    _codeController.dispose();
    super.dispose();
  }

  Future<void> _requestEmailOTP() async {
    setState(() {
      _loading = true;
      _status = 'Requesting email OTP...';
    });

    try {
      final result = await _client.requestOTP(
        recipient: _emailController.text,
        deliveryMethod: DeliveryMethod.DELIVERY_METHOD_EMAIL,
      );
      setState(() {
        _status = 'Email OTP sent! ${result.message}';
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
        _loading = false;
      });
    }
  }

  Future<void> _requestSMSOTP() async {
    setState(() {
      _loading = true;
      _status = 'Requesting SMS OTP...';
    });

    try {
      final result = await _client.requestOTP(
        recipient: _phoneController.text,
        deliveryMethod: DeliveryMethod.DELIVERY_METHOD_SMS,
      );
      setState(() {
        _status = 'SMS OTP sent! ${result.message}';
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
        _loading = false;
      });
    }
  }

  Future<void> _verifyEmailOTP() async {
    setState(() {
      _loading = true;
      _status = 'Verifying email OTP...';
    });

    try {
      final result = await _client.verifyOTP(
        recipient: _emailController.text,
        code: _codeController.text,
      );

      setState(() {
        if (result.hasJwt() && result.hasRefreshToken()) {
          _jwt = result.jwt;
          _status = 'Authenticated! JWT received and saved.';
        } else if (result.hasVerifyToken()) {
          _verifyToken = result.verifyToken;
          _status = 'First factor verified. Please verify phone.';
        } else {
          _status = 'Verification failed: ${result.message}';
        }
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
        _loading = false;
      });
    }
  }

  Future<void> _verifyPhoneOTP() async {
    setState(() {
      _loading = true;
      _status = 'Verifying phone OTP...';
    });

    try {
      final result = await _client.verifyOTP(
        recipient: _phoneController.text,
        code: _codeController.text,
        verifyToken: _verifyToken,
      );

      setState(() {
        if (result.hasJwt() && result.hasRefreshToken()) {
          _jwt = result.jwt;
          _status = 'Authenticated! JWT received and saved.';
        } else {
          _status = 'Verification failed: ${result.message}';
        }
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
        _loading = false;
      });
    }
  }

  Future<void> _signInWithGoogle() async {
    setState(() {
      _loading = true;
      _status = 'Signing in with Google...';
    });

    try {
      final result = await _client.signInWithGoogle();

      setState(() {
        if (result.hasJwt() && result.hasRefreshToken()) {
          _jwt = result.jwt;
          _status = 'Authenticated with Google! JWT received and saved.';
        } else if (result.hasVerifyToken()) {
          _verifyToken = result.verifyToken;
          _status = 'Google sign-in successful. Please verify phone.';
        } else {
          _status = 'Google sign-in failed: ${result.message}';
        }
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
        _loading = false;
      });
    }
  }

  Future<void> _signInWithApple() async {
    setState(() {
      _loading = true;
      _status = 'Signing in with Apple...';
    });

    try {
      final result = await _client.signInWithApple();

      setState(() {
        if (result.hasJwt() && result.hasRefreshToken()) {
          _jwt = result.jwt;
          _status = 'Authenticated with Apple! JWT received and saved.';
        } else if (result.hasVerifyToken()) {
          _verifyToken = result.verifyToken;
          _status = 'Apple sign-in successful. Please verify phone.';
        } else {
          _status = 'Apple sign-in failed: ${result.message}';
        }
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
        _loading = false;
      });
    }
  }

  Future<void> _loadStoredTokens() async {
    setState(() {
      _loading = true;
      _status = 'Loading stored tokens...';
    });

    try {
      final jwt = await _client.getStoredJWT();

      setState(() {
        if (jwt != null) {
          _jwt = jwt;
          _status = 'Loaded JWT from secure storage';
        } else {
          _status = 'No stored tokens found';
        }
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _status = 'Error loading tokens: $e';
        _loading = false;
      });
    }
  }

  Future<void> _refreshStoredToken() async {
    setState(() {
      _loading = true;
      _status = 'Refreshing token...';
    });

    try {
      final success = await _client.refreshStoredToken();

      if (success) {
        final jwt = await _client.getStoredJWT();
        setState(() {
          _jwt = jwt;
          _status = 'Token refreshed successfully!';
          _loading = false;
        });
      } else {
        setState(() {
          _status = 'Failed to refresh token. No refresh token stored?';
          _loading = false;
        });
      }
    } catch (e) {
      setState(() {
        _status = 'Error refreshing token: $e';
        _loading = false;
      });
    }
  }

  Future<void> _clearStoredTokens() async {
    setState(() {
      _loading = true;
      _status = 'Clearing tokens...';
    });

    try {
      await _client.clearTokens();
      setState(() {
        _jwt = null;
        _status = 'Tokens cleared (logged out)';
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _status = 'Error clearing tokens: $e';
        _loading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Vestibule Auth Demo'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'Status',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(_status),
                    if (_verifyToken != null) ...[
                      const SizedBox(height: 8),
                      Text('Verify Token: ${_verifyToken!.substring(0, 20)}...'),
                    ],
                    if (_jwt != null) ...[
                      const SizedBox(height: 8),
                      Text('JWT: ${_jwt!.substring(0, 20)}...'),
                    ],
                  ],
                ),
              ),
            ),
            const SizedBox(height: 24),

            // Email OTP
            const Text(
              'Email OTP',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: _emailController,
              decoration: const InputDecoration(
                labelText: 'Email',
                border: OutlineInputBorder(),
              ),
              keyboardType: TextInputType.emailAddress,
            ),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: _loading ? null : _requestEmailOTP,
              child: const Text('Request Email OTP'),
            ),
            const SizedBox(height: 24),

            // Phone OTP
            const Text(
              'Phone OTP',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: _phoneController,
              decoration: const InputDecoration(
                labelText: 'Phone (+1234567890)',
                border: OutlineInputBorder(),
              ),
              keyboardType: TextInputType.phone,
            ),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: _loading ? null : _requestSMSOTP,
              child: const Text('Request SMS OTP'),
            ),
            const SizedBox(height: 24),

            // Verify OTP
            const Text(
              'Verify OTP',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: _codeController,
              decoration: const InputDecoration(
                labelText: 'OTP Code',
                border: OutlineInputBorder(),
              ),
              keyboardType: TextInputType.number,
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: _loading ? null : _verifyEmailOTP,
                    child: const Text('Verify Email'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: ElevatedButton(
                    onPressed: _loading ? null : _verifyPhoneOTP,
                    child: const Text('Verify Phone'),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 24),

            // Social Sign-In
            const Text(
              'Social Sign-In',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            ElevatedButton.icon(
              onPressed: _loading ? null : _signInWithGoogle,
              icon: const Icon(Icons.g_mobiledata),
              label: const Text('Sign in with Google'),
            ),
            const SizedBox(height: 8),
            ElevatedButton.icon(
              onPressed: _loading ? null : _signInWithApple,
              icon: const Icon(Icons.apple),
              label: const Text('Sign in with Apple'),
            ),
            const SizedBox(height: 24),

            // Token Management
            const Text(
              'Token Management',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: _loading ? null : _loadStoredTokens,
                    child: const Text('Load Tokens'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: ElevatedButton(
                    onPressed: _loading ? null : _refreshStoredToken,
                    child: const Text('Refresh Token'),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: _loading ? null : _clearStoredTokens,
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
              ),
              child: const Text('Clear Tokens (Logout)'),
            ),
          ],
        ),
      ),
    );
  }
}