bapp_auth 0.2.1 copy "bapp_auth: ^0.2.1" to clipboard
bapp_auth: ^0.2.1 copied to clipboard

Cross-platform authentication for BAPP framework platforms with Keycloak SSO and automatic token management

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Bapp Auth Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

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

class _HomePageState extends State<HomePage> {
  final _clientIdController = TextEditingController(text: 'bapp-pos');
  final _hostController =
      TextEditingController(text: 'https://panel.bapp.ro/api');

  BappAuth? _auth;
  String _status = 'Not initialized';
  String _apiResponse = '';

  @override
  void dispose() {
    _clientIdController.dispose();
    _hostController.dispose();
    _auth?.dispose();
    super.dispose();
  }

  BappAuth _getAuth() {
    _auth?.dispose();
    _auth = BappAuth(
      config: BappAuthConfig(
        clientId: _clientIdController.text,
        host: _hostController.text,
        app: 'erp',
      ),
    );

    _auth!.authStateStream.listen((state) {
      if (!mounted) return;
      setState(() {
        switch (state.status) {
          case AuthStatus.initial:
            _status = 'Not initialized';
          case AuthStatus.loading:
            _status = 'Authenticating...';
          case AuthStatus.authenticated:
            _status = 'Authenticated';
          case AuthStatus.unauthenticated:
            _status = state.error ?? 'Not authenticated';
        }
      });
    });

    return _auth!;
  }

  Future<void> _initialize() async {
    try {
      final auth = _getAuth();
      await auth.initialize();
    } catch (e) {
      setState(() => _status = 'Initialization failed: $e');
    }
  }

  Future<void> _loginWithSSO({bool privateMode = false}) async {
    try {
      final auth = _auth ?? _getAuth();
      await auth.loginWithSSO(preferEphemeral: privateMode);
    } catch (e) {
      setState(() => _status = 'SSO login failed: $e');
    }
  }

  Future<void> _loginWithDevice() async {
    try {
      final auth = _auth ?? _getAuth();
      await auth.loginWithDevice(
        onUserAction: (userCode, verificationUri) {
          setState(() {
            _status = 'Go to $verificationUri and enter code: $userCode';
          });
        },
        onStatusUpdate: (status) {
          setState(() => _status = 'Device auth: $status');
        },
      );
    } catch (e) {
      setState(() => _status = 'Device auth failed: $e');
    }
  }

  Future<void> _testMe() async {
    if (_auth == null || !_auth!.isAuthenticated) {
      setState(() => _apiResponse = 'Please authenticate first');
      return;
    }

    try {
      setState(() => _apiResponse = 'Calling /me...');
      final result = await _auth!.apiClient.me();
      setState(() => _apiResponse = 'OK: $result');
    } catch (e) {
      setState(() => _apiResponse = 'FAIL: $e');
    }
  }

  Future<void> _testListTasks() async {
    if (_auth == null || !_auth!.isAuthenticated) {
      setState(() => _apiResponse = 'Please authenticate first');
      return;
    }

    try {
      setState(() => _apiResponse = 'Fetching tasks...');
      final result = await _auth!.apiClient.listTasks();
      setState(() => _apiResponse = 'OK: $result');
    } catch (e) {
      setState(() => _apiResponse = 'FAIL: $e');
    }
  }

  Future<void> _testListContent() async {
    if (_auth == null || !_auth!.isAuthenticated) {
      setState(() => _apiResponse = 'Please authenticate first');
      return;
    }

    try {
      setState(() => _apiResponse = 'Listing content...');
      final result = await _auth!.apiClient.list('core.country');
      setState(
          () => _apiResponse = 'OK: ${result.count} countries');
    } catch (e) {
      setState(() => _apiResponse = 'FAIL: $e');
    }
  }

  Future<void> _switchSession() async {
    final current = _auth?.sessionId;
    final next = current == 'session-a' ? 'session-b' : 'session-a';
    try {
      await _auth?.switchSession(next);
      setState(() {});
    } catch (e) {
      setState(() => _status = 'Switch failed: $e');
    }
  }

  Future<void> _logout() async {
    try {
      await _auth?.logout();
      setState(() => _apiResponse = '');
    } catch (e) {
      setState(() => _status = 'Logout failed: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    final isAuthenticated = _auth?.isAuthenticated ?? false;
    final token = _auth?.currentToken;

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Bapp Auth Example'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Platform Info
            Card(
              color: Colors.blue[50],
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      key: const Key('platform_name'),
                      'Platform: ${PlatformConfig.platformName}',
                      style: const TextStyle(
                          fontSize: 16, fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      key: const Key('redirect_uri'),
                      'Redirect URI: ${PlatformConfig.getRedirectUri()}',
                      style: const TextStyle(
                          fontSize: 12, fontFamily: 'monospace'),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Configuration
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Configuration',
                        style: TextStyle(
                            fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 12),
                    TextField(
                      key: const Key('client_id_field'),
                      controller: _clientIdController,
                      decoration: const InputDecoration(
                        labelText: 'Client ID (Keycloak)',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    const SizedBox(height: 12),
                    TextField(
                      key: const Key('host_field'),
                      controller: _hostController,
                      decoration: const InputDecoration(
                        labelText: 'BAPP API Host',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    const SizedBox(height: 12),
                    ElevatedButton.icon(
                      key: const Key('btn_initialize'),
                      onPressed: _initialize,
                      icon: const Icon(Icons.play_arrow),
                      label: const Text('Initialize'),
                      style: ElevatedButton.styleFrom(
                        minimumSize: const Size(double.infinity, 36),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Status
            Text(
              key: const Key('auth_status'),
              _status,
              style: TextStyle(
                color: isAuthenticated ? Colors.green : Colors.orange,
                fontWeight: FontWeight.w500,
                fontSize: 16,
              ),
            ),
            const SizedBox(height: 12),

            // Authentication buttons
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Authentication',
                        style: TextStyle(
                            fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 12),
                    Row(
                      children: [
                        Expanded(
                          child: ElevatedButton.icon(
                            key: const Key('btn_sso'),
                            onPressed: !isAuthenticated
                                ? () => _loginWithSSO()
                                : null,
                            icon: const Icon(Icons.login),
                            label: const Text('SSO Login'),
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: ElevatedButton.icon(
                            key: const Key('btn_sso_private'),
                            onPressed: !isAuthenticated
                                ? () => _loginWithSSO(privateMode: true)
                                : null,
                            icon: const Icon(Icons.privacy_tip),
                            label: const Text('SSO (Private)'),
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    ElevatedButton.icon(
                      key: const Key('btn_device'),
                      onPressed: !isAuthenticated ? _loginWithDevice : null,
                      icon: const Icon(Icons.devices),
                      label: const Text('Device Flow'),
                      style: ElevatedButton.styleFrom(
                        minimumSize: const Size(double.infinity, 36),
                      ),
                    ),
                    const SizedBox(height: 8),
                    ElevatedButton.icon(
                      key: const Key('btn_switch_session'),
                      onPressed: _auth != null ? _switchSession : null,
                      icon: const Icon(Icons.swap_horiz),
                      label: Text(_auth?.sessionId != null
                          ? 'Switch Session (current: ${_auth!.sessionId})'
                          : 'Switch Session'),
                      style: ElevatedButton.styleFrom(
                        minimumSize: const Size(double.infinity, 36),
                      ),
                    ),
                    if (isAuthenticated) ...[
                      const SizedBox(height: 8),
                      ElevatedButton.icon(
                        key: const Key('btn_logout'),
                        onPressed: _logout,
                        icon: const Icon(Icons.logout),
                        label: const Text('Logout'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.red,
                          foregroundColor: Colors.white,
                          minimumSize: const Size(double.infinity, 36),
                        ),
                      ),
                    ],
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // API Testing
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('API Testing',
                        style: TextStyle(
                            fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 12),
                    Wrap(
                      spacing: 8,
                      runSpacing: 8,
                      children: [
                        ElevatedButton(
                          key: const Key('btn_me'),
                          onPressed: isAuthenticated ? _testMe : null,
                          child: const Text('Me'),
                        ),
                        ElevatedButton(
                          key: const Key('btn_tasks'),
                          onPressed: isAuthenticated ? _testListTasks : null,
                          child: const Text('List Tasks'),
                        ),
                        ElevatedButton(
                          key: const Key('btn_countries'),
                          onPressed: isAuthenticated ? _testListContent : null,
                          child: const Text('List Countries'),
                        ),
                      ],
                    ),
                    const SizedBox(height: 12),
                    Container(
                      key: const Key('api_response'),
                      width: double.infinity,
                      padding: const EdgeInsets.all(8),
                      decoration: BoxDecoration(
                        color: Colors.grey[100],
                        borderRadius: BorderRadius.circular(4),
                      ),
                      child: Text(
                        _apiResponse.isEmpty ? '(no response)' : _apiResponse,
                        style: const TextStyle(
                            fontFamily: 'monospace', fontSize: 12),
                      ),
                    ),
                  ],
                ),
              ),
            ),

            // Token Info
            if (token != null) ...[
              const SizedBox(height: 16),
              Card(
                key: const Key('token_info'),
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('Token Info',
                          style: TextStyle(
                              fontSize: 18, fontWeight: FontWeight.bold)),
                      const SizedBox(height: 12),
                      Text('Token Type: ${token.tokenType}'),
                      Text('Expires In: ${token.expiresIn}s'),
                      Text('Is Expired: ${token.isExpired}'),
                      Text('Is Expiring Soon: ${token.isExpiringSoon}'),
                      if (token.scope != null) Text('Scope: ${token.scope}'),
                    ],
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}
1
likes
160
points
35
downloads

Documentation

API reference

Publisher

verified publishercbsoft.ro

Weekly Downloads

Cross-platform authentication for BAPP framework platforms with Keycloak SSO and automatic token management

Homepage

License

MIT (license)

Dependencies

bapp_api_client, crypto, flutter, flutter_web_auth_2, http, shared_preferences, url_launcher

More

Packages that depend on bapp_auth