flutter_secure_auth 0.1.7 copy "flutter_secure_auth: ^0.1.7" to clipboard
flutter_secure_auth: ^0.1.7 copied to clipboard

A secure, lightweight authentication package for Flutter (REST + OAuth2 PKCE + token refresh) with secure local storage.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_secure_auth/flutter_secure_auth.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() => runApp(const PetStoreApp());

/// A demo product class representing items in the pet store.
class Product {
  final String id;
  final String name;
  final String category;
  final double price;
  final String icon;
  final String description;

  const Product({
    required this.id,
    required this.name,
    required this.category,
    required this.price,
    required this.icon,
    required this.description,
  });
}

/// The main application entry point showcasing the Pet Supplies Store MVP.
class PetStoreApp extends StatelessWidget {
  const PetStoreApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Paws & Claws Supplies',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF2A2D43),
          primary: const Color(0xFF2A2D43),
          secondary: const Color(0xFFF3A712),
          background: const Color(0xFFF9F9FB),
        ),
        cardTheme: const CardTheme(
          elevation: 2,
          color: Colors.white,
        ),
      ),
      home: const PetStoreCatalogScreen(),
    );
  }
}

/// The main catalog screen of the pet store featuring product grids,
/// category filters, and a slide-out cart sheet.
class PetStoreCatalogScreen extends StatefulWidget {
  const PetStoreCatalogScreen({super.key});

  @override
  State<PetStoreCatalogScreen> createState() => _PetStoreCatalogScreenState();
}

class _AuthMockClient extends http.BaseClient {
  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    // Return a mocked successful authentication response for demonstration
    final payload = {
      'access_token': 'mocked_jwt_token_for_pet_store_checkout',
      'refresh_token': 'mocked_refresh_token',
      'expires_in': 3600,
    };
    return http.StreamedResponse(
      Stream.fromIterable([utf8.encode(jsonEncode(payload))]),
      200,
      headers: {'content-type': 'application/json'},
    );
  }
}

class _PetStoreCatalogScreenState extends State<PetStoreCatalogScreen> {
  // Initialize our newly updated secure auth service
  late final AuthService authService;
  
  final List<Product> _products = const [
    Product(
      id: 'p1',
      name: 'Premium Puppy Kibble',
      category: 'Dogs',
      price: 49.99,
      icon: '🐶',
      description: 'Organic grain-free kibble rich in proteins for healthy puppy growth.',
    ),
    Product(
      id: 'p2',
      name: 'Cat Feather wand Toy',
      category: 'Cats',
      price: 12.49,
      icon: '🐱',
      description: 'Interactive teaser wand to keep your cat active and entertained.',
    ),
    Product(
      id: 'p3',
      name: 'Orchard Grass Hay',
      category: 'Rabbits',
      price: 18.99,
      icon: '🐰',
      description: 'Sweet, soft hay ideal for rabbits, guinea pigs, and small pets.',
    ),
    Product(
      id: 'p4',
      name: 'Ergonomic Dog Harness',
      category: 'Dogs',
      price: 34.99,
      icon: '🐕',
      description: 'No-pull breathable chest harness with reflective security strips.',
    ),
    Product(
      id: 'p5',
      name: 'Self-Cleaning Litter Mat',
      category: 'Cats',
      price: 22.99,
      icon: '🐈',
      description: 'Double-layer honeycomb design traps litter scatter effectively.',
    ),
    Product(
      id: 'p6',
      name: 'Premium Bird Seed Mix',
      category: 'Birds',
      price: 14.99,
      icon: '🦜',
      description: 'Natural blend of seeds, nuts, and dried fruits for colorful birds.',
    ),
  ];

  final Map<Product, int> _cart = {};
  String _selectedCategory = 'All';
  bool _isCheckingOut = false;
  AuthTokens? _userSession;

  @override
  void initState() {
    super.initState();
    authService = AuthService(
      tokenEndpoint: Uri.parse('https://api.example.com/oauth/token'),
      httpClientFactory: () => _AuthMockClient(),
    );
    _checkExistingSession();
  }

  Future<void> _checkExistingSession() async {
    final tokens = await authService.currentTokens();
    if (mounted) {
      setState(() => _userSession = tokens);
    }
  }

  void _addToCart(Product product) {
    setState(() {
      _cart[product] = (_cart[product] ?? 0) + 1;
    });
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('${product.name} added to cart!'),
        duration: const Duration(seconds: 1),
        behavior: SnackBarBehavior.floating,
        backgroundColor: const Color(0xFF2A2D43),
      ),
    );
  }

  void _removeFromCart(Product product) {
    setState(() {
      if (_cart.containsKey(product)) {
        if (_cart[product] == 1) {
          _cart.remove(product);
        } else {
          _cart[product] = _cart[product]! - 1;
        }
      }
    });
  }

  double get _cartTotal =>
      _cart.entries.fold(0, (sum, entry) => sum + (entry.key.price * entry.value));

  int get _cartItemCount =>
      _cart.values.fold(0, (sum, count) => sum + count);

  Future<void> _handleCheckout() async {
    // If not logged in, trigger auth flow using flutter_secure_auth
    if (_userSession == null) {
      final success = await _showLoginDialog();
      if (!success) return;
    }

    setState(() => _isCheckingOut = true);
    // Simulate ordering network request delay
    await Future.delayed(const Duration(seconds: 2));

    if (mounted) {
      setState(() {
        _isCheckingOut = false;
        _cart.clear();
      });

      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          icon: const Icon(Icons.check_circle, size: 60, color: Colors.green),
          title: const Text('Order Confirmed!'),
          content: const Text(
            'Your checkout was completed securely. We have sent a confirmation details invoice to your email.',
            textAlign: TextAlign.center,
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('Great'),
            ),
          ],
        ),
      );
    }
  }

  Future<bool> _showLoginDialog() async {
    final usernameController = TextEditingController(text: 'customer@petstore.com');
    final passwordController = TextEditingController(text: 'securepassword123');
    bool isLoading = false;
    String? errorMessage;

    final result = await showDialog<bool>(
      context: context,
      barrierDismissible: false,
      builder: (context) {
        return StatefulBuilder(
          builder: (context, setDialogState) {
            return AlertDialog(
              title: Row(
                children: [
                  Icon(Icons.lock_outline, color: Theme.of(context).primaryColor),
                  const SizedBox(width: 10),
                  const Text('Secure Checkout Login'),
                ],
              ),
              content: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Text(
                    'To complete your order, log in or sign up securely. (Try submitting the mocked form below!)',
                    style: TextStyle(fontSize: 12, color: Colors.grey),
                  ),
                  const SizedBox(height: 16),
                  TextField(
                    controller: usernameController,
                    decoration: const InputDecoration(
                      labelText: 'Email Address',
                      prefixIcon: Icon(Icons.email_outlined),
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 12),
                  TextField(
                    controller: passwordController,
                    obscureText: true,
                    decoration: const InputDecoration(
                      labelText: 'Password',
                      prefixIcon: Icon(Icons.lock_outline),
                      border: OutlineInputBorder(),
                    ),
                  ),
                  if (errorMessage != null) ...[
                    const SizedBox(height: 12),
                    Text(
                      errorMessage!,
                      style: const TextStyle(color: Colors.red, fontSize: 12),
                    ),
                  ],
                  if (isLoading) ...[
                    const SizedBox(height: 16),
                    const CircularProgressIndicator(),
                  ],
                ],
              ),
              actions: [
                TextButton(
                  onPressed: isLoading ? null : () => Navigator.pop(context, false),
                  child: const Text('Cancel'),
                ),
                ElevatedButton(
                  onPressed: isLoading
                      ? null
                      : () async {
                          setDialogState(() {
                            isLoading = true;
                            errorMessage = null;
                          });

                          try {
                            final tokens = await authService.signInWithPassword(
                              endpoint: Uri.parse('https://api.example.com/login'),
                              username: usernameController.text,
                              password: passwordController.text,
                            );
                            if (context.mounted) {
                              setState(() => _userSession = tokens);
                              Navigator.pop(context, true);
                            }
                          } catch (e) {
                            setDialogState(() {
                              isLoading = false;
                              errorMessage = 'Authentication failed: $e';
                            });
                          }
                        },
                  child: const Text('Log In & Checkout'),
                ),
              ],
            );
          },
        );
      },
    );

    return result ?? false;
  }

  Future<void> _handleLogout() async {
    await authService.signOut();
    setState(() {
      _userSession = null;
    });
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Logged out successfully')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    final filteredProducts = _selectedCategory == 'All'
        ? _products
        : _products.where((p) => p.category == _selectedCategory).toList();

    return Scaffold(
      appBar: AppBar(
        title: const Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Paws & Claws Supplies',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
            ),
            Text(
              'Pet Store App MVP Demo',
              style: TextStyle(fontSize: 12, color: Colors.grey),
            ),
          ],
        ),
        actions: [
          if (_userSession != null)
            IconButton(
              icon: const Icon(Icons.logout, color: Colors.redAccent),
              tooltip: 'Log out secure session',
              onPressed: _handleLogout,
            )
          else
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 16),
              child: Icon(Icons.lock_open, color: Colors.grey),
            )
        ],
      ),
      body: Column(
        children: [
          // Category Selector
          Container(
            height: 60,
            padding: const EdgeInsets.symmetric(vertical: 8),
            child: ListView(
              scrollDirection: Axis.horizontal,
              children: ['All', 'Dogs', 'Cats', 'Birds', 'Rabbits'].map((category) {
                final isSelected = _selectedCategory == category;
                return Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 8),
                  child: ChoiceChip(
                    label: Text(category),
                    selected: isSelected,
                    selectedColor: const Color(0xFF2A2D43),
                    labelStyle: TextStyle(
                      color: isSelected ? Colors.white : Colors.black87,
                      fontWeight: FontWeight.bold,
                    ),
                    onSelected: (val) {
                      setState(() => _selectedCategory = category);
                    },
                  ),
                );
              }).toList(),
            ),
          ),

          // Product Grid Listing
          Expanded(
            child: GridView.builder(
              padding: const EdgeInsets.all(12),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                childAspectRatio: 0.72,
                crossAxisSpacing: 12,
                mainAxisSpacing: 12,
              ),
              itemCount: filteredProducts.length,
              itemBuilder: (context, index) {
                final product = filteredProducts[index];
                return Card(
                  clipBehavior: Clip.antiAlias,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      // Product Cover / Icon Box
                      Expanded(
                        child: Container(
                          width: double.infinity,
                          color: Theme.of(context).colorScheme.primary.withOpacity(0.04),
                          alignment: Alignment.center,
                          child: Text(
                            product.icon,
                            style: const TextStyle(fontSize: 60),
                          ),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.all(10),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              product.name,
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: const TextStyle(
                                fontWeight: FontWeight.bold,
                                fontSize: 14,
                              ),
                            ),
                            const SizedBox(height: 4),
                            Text(
                              product.description,
                              maxLines: 2,
                              overflow: TextOverflow.ellipsis,
                              style: const TextStyle(
                                fontSize: 11,
                                color: Colors.grey,
                              ),
                            ),
                            const SizedBox(height: 8),
                            Row(
                              mainAxisAlignment: MainAxisAlignment.between,
                              children: [
                                Text(
                                  '\$${product.price.toStringAsFixed(2)}',
                                  style: const TextStyle(
                                    fontWeight: FontWeight.bold,
                                    color: Color(0xFFF3A712),
                                    fontSize: 15,
                                  ),
                                ),
                                IconButton(
                                  icon: const Icon(Icons.add_shopping_cart),
                                  constraints: const BoxConstraints(),
                                  padding: EdgeInsets.zero,
                                  color: const Color(0xFF2A2D43),
                                  onPressed: () => _addToCart(product),
                                ),
                              ],
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
        ],
      ),
      bottomNavigationBar: _cart.isEmpty
          ? null
          : Container(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
              decoration: BoxDecoration(
                color: Colors.white,
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withOpacity(0.08),
                    blurRadius: 10,
                    offset: const Offset(0, -4),
                  ),
                ],
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.between,
                children: [
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Total ($_cartItemCount Items)',
                        style: const TextStyle(fontSize: 12, color: Colors.grey),
                      ),
                      Text(
                        '\$${_cartTotal.toStringAsFixed(2)}',
                        style: const TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.bold,
                          color: Color(0xFF2A2D43),
                        ),
                      ),
                    ],
                  ),
                  ElevatedButton(
                    onPressed: () {
                      _showCartSheet();
                    },
                    style: ElevatedButton.styleFrom(
                      backgroundColor: const Color(0xFFF3A712),
                      foregroundColor: Colors.white,
                      padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                    ),
                    child: const Text(
                      'View Cart & Checkout',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                ],
              ),
            ),
    );
  }

  void _showCartSheet() {
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      builder: (context) {
        return StatefulBuilder(
          builder: (context, setSheetState) {
            return DraggableScrollableSheet(
              initialChildSize: 0.6,
              maxChildSize: 0.9,
              minChildSize: 0.4,
              expand: false,
              builder: (context, scrollController) {
                return Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Center(
                        child: Container(
                          width: 40,
                          height: 4,
                          margin: const EdgeInsets.only(bottom: 16),
                          decoration: BoxDecoration(
                            color: Colors.grey[300],
                            borderRadius: BorderRadius.circular(2),
                          ),
                        ),
                      ),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.between,
                        children: [
                          const Text(
                            'Your Cart',
                            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                          ),
                          if (_userSession != null)
                            const Chip(
                              avatar: Icon(Icons.verified, size: 16, color: Colors.green),
                              label: Text('Secure Session Active', style: TextStyle(fontSize: 10)),
                            ),
                        ],
                      ),
                      const SizedBox(height: 16),
                      Expanded(
                        child: _cart.isEmpty
                            ? const Center(child: Text('Your cart is empty.'))
                            : ListView.builder(
                                controller: scrollController,
                                itemCount: _cart.length,
                                itemBuilder: (context, index) {
                                  final product = _cart.keys.elementAt(index);
                                  final quantity = _cart[product]!;
                                  return ListTile(
                                    leading: Text(product.icon, style: const TextStyle(fontSize: 28)),
                                    title: Text(product.name),
                                    subtitle: Text('\$${product.price} x $quantity'),
                                    trailing: Row(
                                      mainAxisSize: MainAxisSize.min,
                                      children: [
                                        IconButton(
                                          icon: const Icon(Icons.remove_circle_outline),
                                          onPressed: () {
                                            _removeFromCart(product);
                                            setSheetState(() {});
                                            setState(() {});
                                          },
                                        ),
                                        Text('$quantity'),
                                        IconButton(
                                          icon: const Icon(Icons.add_circle_outline),
                                          onPressed: () {
                                            _addToCart(product);
                                            setSheetState(() {});
                                            setState(() {});
                                          },
                                        ),
                                      ],
                                    ),
                                  );
                                },
                              ),
                      ),
                      const Divider(),
                      Padding(
                        padding: const EdgeInsets.symmetric(vertical: 8),
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.between,
                          children: [
                            const Text('Total amount:', style: TextStyle(fontWeight: FontWeight.bold)),
                            Text(
                              '\$${_cartTotal.toStringAsFixed(2)}',
                              style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                            ),
                          ],
                        ),
                      ),
                      SizedBox(
                        width: double.infinity,
                        height: 48,
                        child: ElevatedButton(
                          onPressed: _cart.isEmpty || _isCheckingOut
                              ? null
                              : () async {
                                  Navigator.pop(context); // Close bottom sheet
                                  await _handleCheckout();
                                },
                          style: ElevatedButton.styleFrom(
                            backgroundColor: const Color(0xFF2A2D43),
                            foregroundColor: Colors.white,
                          ),
                          child: _isCheckingOut
                              ? const CircularProgressIndicator(color: Colors.white)
                              : const Text('Proceed to Secure Checkout', style: TextStyle(fontWeight: FontWeight.bold)),
                        ),
                      ),
                    ],
                  ),
                );
              },
            );
          },
        );
      },
    );
  }
}
4
likes
160
points
331
downloads

Documentation

API reference

Publisher

verified publisherquinttechco.com

Weekly Downloads

A secure, lightweight authentication package for Flutter (REST + OAuth2 PKCE + token refresh) with secure local storage.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

crypto, flutter, flutter_secure_storage, http, json_annotation, lints, uuid

More

Packages that depend on flutter_secure_auth