Skip to main content
Turnkey’s Flutter SDK makes authentication simple. You can call specific login and signup functions (email/SMS OTP, passkeys, or social logins) to build your own UI and auth flow.

Authentication state

To check if a user is authenticated, you can use the authState variable from the TurnkeyProvider. authState can be one of the following:
  • AuthState.authenticated: The user has a stored session.
  • AuthState.unauthenticated: The user does not have a stored session.
  • AuthState.loading: The provider is initializing. Turnkey is checking for any existing stored sessions or restoring state. UI should show a splash or progress indicator.
You can consume authState in any widget using a Consumer<TurnkeyProvider>:
lib/widgets/auth_status.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart';

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

  @override
  Widget build(BuildContext context) {
    return Consumer<TurnkeyProvider>(
      builder: (context, tk, _) {
        switch (tk.authState) {
          case AuthState.loading:
            return const Center(
              child: CircularProgressIndicator(),
            );

          case AuthState.unauthenticated:
            return ElevatedButton(
              onPressed: () {
                // Navigate to your login screen or trigger your chosen auth flow
                Navigator.of(context).pushNamed('/login');
              },
              child: const Text('Log in'),
            );

          case AuthState.authenticated:
            final user = tk.user;
            return Text('Welcome back, ${user?.userName ?? 'user'}!');
        }
      },
    );
  }
}
You can also set up an onSessionSelected callback in TurnkeyConfig to handle post-authentication logic, such as redirecting.
snippet
final turnkeyProvider = TurnkeyProvider(
  config: TurnkeyConfig(
    // ... your existing config ...
    onSessionSelected: (session) {
      // User authenticated — perform navigation or data preloads
      navigatorKey.currentState?.pushReplacementNamed('/dashboard');
    },
  ),
);

Listening to provider updates

TurnkeyProvider is a ChangeNotifier, so you can attach listeners to respond to internal state changes (e.g., when user/wallets are fetched or when authState changes). This pattern is useful when you want to mirror provider data into local widget state. Below is an example (adapted from the demo app) that keeps a local selectedWallet/selectedAccount in sync with the provider:
lib/screens/dashboard.dart
class DashboardScreenState extends State<DashboardScreen> {
  Wallet? _selectedWallet;
  v1WalletAccount? _selectedAccount;

  late final TurnkeyProvider _turnkeyProvider;
  late final VoidCallback _providerListener;

  @override
  void initState() {
    super.initState();

    // Capture provider once; no context in listener later.
    _turnkeyProvider = Provider.of<TurnkeyProvider>(context, listen: false);

    _providerListener = _handleProviderUpdate;
    _turnkeyProvider.addListener(_providerListener);

    // Initialize local selections from provider.
    _updateSelectedWalletFromProvider(_turnkeyProvider);
  }

  @override
  void dispose() {
    // Always remove listeners to avoid calling setState on a disposed widget.
    _turnkeyProvider.removeListener(_providerListener);
    super.dispose();
  }

  void _handleProviderUpdate() {
    if (!context.mounted) return;
    _updateSelectedWalletFromProvider(_turnkeyProvider);
  }

  void _updateSelectedWalletFromProvider(TurnkeyProvider provider) {
    final wallets = provider.wallets;
    final user = provider.user;

    if (user == null || wallets == null || wallets.isEmpty) return;
    if (wallets.first.accounts.isEmpty) return;

    if (!context.mounted) return;
    setState(() {
      _selectedWallet = wallets.first;
      _selectedAccount = wallets.first.accounts.first;
    });
  }
}
Tips
  • Use addListener/removeListener for one-off reactions to state changes.
  • Use Consumer, Selector, or context.select when you want automatic rebuilds based on specific slices of provider state.
  • Avoid calling setState inside your listener after the widget is disposed — always guard with if (!mounted) return; or if (!context.mounted) return;.

Customize sub-organization creation

Need to configure default user names, passkey names, wallet creations or anything sub-org related? You can learn more about customizing the sub-orgs you create in the Sub-Organization Customization. Follow the guides below to learn how to set up email and SMS authentication, passkey authentication, and social logins in your Flutter app.