Skip to main content

ab_ecosystem_sso 0.1.0 copy "ab_ecosystem_sso: ^0.1.0" to clipboard
ab_ecosystem_sso: ^0.1.0 copied to clipboard

Flutter SDK for AB Ecosystem SSO — drop-in sign-in and sign-up widgets, 2FA, multi-account switching, captcha and secure session storage.

example/lib/main.dart

import 'package:ab_ecosystem_sso/ab_ecosystem_sso.dart';
import 'package:flutter/material.dart';

/// Point these at your own project. Both are required: the SDK is multi-tenant
/// and has no default for either.
const String kProjectId = 'YOUR_PROJECT_ID';
final Uri kBaseUrl = Uri.parse('https://api.ab-ecosystem.com');

final SsoClient sso = SsoClient(
  SsoConfig(projectId: kProjectId, baseUrl: kBaseUrl),
);

Future<void> main() async {
  // Reads the stored token and checks it with the server. Awaiting it before
  // the first frame is what stops the app flashing the login screen for a
  // user who is already signed in.
  await sso.init();
  runApp(SsoScope(client: sso, child: const ExampleApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SSO example',
      theme: ThemeData(colorSchemeSeed: Colors.indigo),
      darkTheme: ThemeData.dark(useMaterial3: true),
      home: const Gate(),
    );
  }
}

/// Shows the auth flow or the app, depending on the session.
///
/// [SsoScope.sessionOf] rebuilds this whenever the session changes, so signing
/// in, signing out and a session going stale while backgrounded all land here.
class Gate extends StatelessWidget {
  const Gate({super.key});

  @override
  Widget build(BuildContext context) {
    final SsoSession session = SsoScope.sessionOf(context);

    return switch (session) {
      SsoUnknown() || SsoValidating() => const Scaffold(
          body: Center(child: CircularProgressIndicator()),
        ),
      SsoSignedIn() => const HomePage(),
      // The login is still good — only this account went away. Offer the other
      // accounts rather than signing the user out.
      SsoAccountAccessLost(:final String? message) => Scaffold(
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              if (message != null)
                Padding(
                  padding: const EdgeInsets.all(24),
                  child: Text(message, textAlign: TextAlign.center),
                ),
              Expanded(
                child: SsoAccountPicker.forSwitching(onSignedIn: (_) {}),
              ),
            ],
          ),
        ),
      SsoSignedOut() => Scaffold(
          body: SsoAuthFlow(
            // The session notifier already drives this screen, so there is
            // nothing to do here but let the rebuild happen.
            onSignedIn: (String token) {},
          ),
        ),
    };
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late Future<SsoProfile> _profile = SsoScope.of(context).profile();

  @override
  Widget build(BuildContext context) {
    final SsoClient client = SsoScope.of(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Signed in'),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.settings_outlined),
            tooltip: 'Settings',
            onPressed: () async {
              // One screen for profile, password, 2FA, billing and — for an
              // owner — roles, members and invitations.
              await showSsoAccountSettings(
                context,
                inviteLinkBase: 'https://example.com/accept-invite',
                onAccountSwitch: (_) =>
                    setState(() => _profile = client.profile()),
              );
              if (context.mounted) {
                setState(() => _profile = client.profile());
              }
            },
          ),
          IconButton(
            icon: const Icon(Icons.logout),
            tooltip: 'Sign out',
            onPressed: client.logout,
          ),
        ],
      ),
      body: FutureBuilder<SsoProfile>(
        future: _profile,
        builder: (BuildContext context, AsyncSnapshot<SsoProfile> snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text('${snapshot.error}'),
                  TextButton(
                    onPressed: () =>
                        setState(() => _profile = client.profile()),
                    child: const Text('Try again'),
                  ),
                ],
              ),
            );
          }

          final SsoProfile profile = snapshot.data!;
          return ListView(
            padding: const EdgeInsets.all(16),
            children: <Widget>[
              ListTile(
                title: const Text('Name'),
                subtitle: Text(profile.displayName),
              ),
              ListTile(
                  title: const Text('Email'), subtitle: Text(profile.email)),
              ListTile(
                title: const Text('Account'),
                subtitle: Text(client.context?.accountName ?? '—'),
              ),
              ListTile(
                title: const Text('Role'),
                subtitle: Text(
                  client.context?.isOwner == true
                      ? 'Owner'
                      : client.context?.role?.title ?? 'Member',
                ),
              ),
              const Divider(),
              ListTile(
                title: const Text('Switch account'),
                trailing: const Icon(Icons.chevron_right),
                onTap: () => showModalBottomSheet<void>(
                  context: context,
                  builder: (BuildContext sheetContext) => Padding(
                    padding: const EdgeInsets.all(16),
                    child: SsoAccountPicker.forSwitching(
                      embedded: true,
                      onSignedIn: (_) {
                        Navigator.of(sheetContext).pop();
                        setState(() => _profile = client.profile());
                      },
                    ),
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}
0
likes
160
points
469
downloads

Documentation

API reference

Publisher

verified publisherab-ecosystem.com

Weekly Downloads

Flutter SDK for AB Ecosystem SSO — drop-in sign-in and sign-up widgets, 2FA, multi-account switching, captcha and secure session storage.

Homepage

Topics

#authentication #sso #login

License

MIT (license)

Dependencies

flutter, flutter_secure_storage, http, webview_flutter

More

Packages that depend on ab_ecosystem_sso