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

Cross-platform OAuth package for Bapp authentication with Keycloak integration and API client

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 _baseUrlController = TextEditingController(text: 'https://panel.bapp.ro/api/');

  KeycloakAuth? _keycloakAuth;
  BappApiClient? _apiClient;
  TokenResponse? _token;
  String _status = 'Not authenticated';
  String _apiResponse = '';

  @override
  void dispose() {
    _clientIdController.dispose();
    _baseUrlController.dispose();
    _apiClient?.close();
    super.dispose();
  }

  KeycloakAuth _getAuth() {
    return _keycloakAuth ??= KeycloakAuth(
      hostname: 'id.bapp.ro',
      realm: 'bapp',
      clientId: _clientIdController.text,
    );
  }

  Future<void> _authenticateWithSSO({bool privateMode = false}) async {
    try {
      setState(() {
        _status = privateMode
            ? 'Starting SSO (Private Mode) on ${PlatformConfig.platformName}...'
            : 'Starting SSO on ${PlatformConfig.platformName}...';
      });

      final auth = _getAuth();
      final redirectUri = PlatformConfig.getRedirectUri();

      setState(() {
        _status = privateMode
            ? 'Using redirect URI: $redirectUri (Private Mode - Fresh Login)'
            : 'Using redirect URI: $redirectUri (Will reuse browser session if logged in)';
      });

      final token = await auth.authenticateWithSSO(
        redirectUri: redirectUri,
        preferEphemeral: privateMode,
      );

      await auth.saveToken(token);

      setState(() {
        _token = token;
        _status = 'Authenticated via SSO';
      });

      _initializeApiClient();
    } catch (e) {
      setState(() {
        _status = 'SSO authentication failed: $e';
      });
    }
  }

  Future<void> _authenticateWithDevice() async {
    try {
      setState(() {
        _status = 'Starting device authentication...';
      });

      final auth = _getAuth();
      final token = await auth.authenticateWithDevice(
        onUserAction: (userCode, verificationUri) {
          setState(() {
            _status = 'Go to $verificationUri and enter code: $userCode';
          });
        },
        onStatusUpdate: (status) {
          setState(() {
            _status = 'Device auth: $status';
          });
        },
      );

      await auth.saveToken(token);

      setState(() {
        _token = token;
        _status = 'Authenticated via Device Flow';
      });

      _initializeApiClient();
    } catch (e) {
      setState(() {
        _status = 'Device authentication failed: $e';
      });
    }
  }

  void _initializeApiClient() {
    if (_token != null) {
      _apiClient?.close();
      _apiClient = BappApiClient(
        baseUrl: _baseUrlController.text,
        bearer: _token!.accessToken,
        app: 'erp',
      );
    }
  }

  Future<void> _testApiCall() async {
    if (_apiClient == null) {
      setState(() {
        _apiResponse = 'Please authenticate first';
      });
      return;
    }

    try {
      setState(() {
        _apiResponse = 'Calling API...';
      });

      final result = await _apiClient!.me();

      setState(() {
        _apiResponse = 'API Response:\n${result.toString()}';
      });
    } catch (e) {
      setState(() {
        _apiResponse = 'API call failed: $e';
      });
    }
  }

  Future<void> _testGetAvailableTasks() async {
    if (_apiClient == null) {
      setState(() {
        _apiResponse = 'Please authenticate first';
      });
      return;
    }

    try {
      setState(() {
        _apiResponse = 'Fetching available tasks...';
      });

      final result = await _apiClient!.getAvailableTasks();

      setState(() {
        _apiResponse = 'Available Tasks:\n${result.toString()}';
      });
    } catch (e) {
      setState(() {
        _apiResponse = 'Failed to fetch tasks: $e';
      });
    }
  }

  Future<void> _testListContentType() async {
    if (_apiClient == null) {
      setState(() {
        _apiResponse = 'Please authenticate first';
      });
      return;
    }

    try {
      setState(() {
        _apiResponse = 'Listing content...';
      });

      // Example: List first page of a content type
      final result = await _apiClient!.list('example.model');

      setState(() {
        _apiResponse = 'Content List:\n${result.toString()}';
      });
    } catch (e) {
      setState(() {
        _apiResponse = 'Failed to list content: $e';
      });
    }
  }

  Future<void> _logout() async {
    try {
      if (_token?.refreshToken != null) {
        final auth = _getAuth();
        await auth.logout(refreshToken: _token!.refreshToken!);
        await auth.clearToken();
      }

      _apiClient?.close();

      setState(() {
        _token = null;
        _apiClient = null;
        _status = 'Logged out';
        _apiResponse = '';
      });
    } catch (e) {
      setState(() {
        _status = 'Logout failed: $e';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    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 section
            Card(
              color: Colors.blue[50],
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        const Icon(Icons.info_outline, color: Colors.blue),
                        const SizedBox(width: 8),
                        Text(
                          'Platform: ${PlatformConfig.platformName}',
                          style: const TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'Redirect URI: ${PlatformConfig.getRedirectUri()}',
                      style: const TextStyle(fontSize: 12, fontFamily: 'monospace'),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      PlatformConfig.supportsCustomScheme
                        ? '✓ Custom URL scheme supported'
                        : 'ℹ Using localhost redirect',
                      style: TextStyle(
                        fontSize: 12,
                        color: PlatformConfig.supportsCustomScheme ? Colors.green[700] : Colors.orange[700],
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Configuration section
            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(
                      controller: _clientIdController,
                      decoration: const InputDecoration(
                        labelText: 'Client ID',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    const SizedBox(height: 12),
                    TextField(
                      controller: _baseUrlController,
                      decoration: const InputDecoration(
                        labelText: 'API Base URL',
                        border: OutlineInputBorder(),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Authentication section
            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),
                    Text(
                      _status,
                      style: TextStyle(
                        color: _token != null ? Colors.green : Colors.orange,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Container(
                      padding: const EdgeInsets.all(8),
                      decoration: BoxDecoration(
                        color: Colors.blue[50],
                        borderRadius: BorderRadius.circular(4),
                        border: Border.all(color: Colors.blue[200]!),
                      ),
                      child: const Row(
                        children: [
                          Icon(Icons.info, size: 16, color: Colors.blue),
                          SizedBox(width: 8),
                          Expanded(
                            child: Text(
                              'SSO uses your browser. If you\'re already logged in to Keycloak, you won\'t need to enter credentials!',
                              style: TextStyle(fontSize: 11, color: Colors.blue),
                            ),
                          ),
                        ],
                      ),
                    ),
                    const SizedBox(height: 12),
                    Row(
                      children: [
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: _token == null ? () => _authenticateWithSSO() : null,
                            icon: const Icon(Icons.login),
                            label: const Text('SSO Login'),
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: _token == null ? () => _authenticateWithSSO(privateMode: true) : null,
                            icon: const Icon(Icons.privacy_tip),
                            label: const Text('SSO (Private)'),
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    ElevatedButton.icon(
                      onPressed: _token == null ? _authenticateWithDevice : null,
                      icon: const Icon(Icons.devices),
                      label: const Text('Device Authentication Flow'),
                      style: ElevatedButton.styleFrom(
                        minimumSize: const Size(double.infinity, 36),
                      ),
                    ),
                    if (_token != null) ...[
                      const SizedBox(height: 8),
                      ElevatedButton.icon(
                        onPressed: _logout,
                        icon: const Icon(Icons.logout),
                        label: const Text('Logout'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.red,
                          foregroundColor: Colors.white,
                        ),
                      ),
                    ],
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // API Testing section
            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(
                          onPressed: _token != null ? _testApiCall : null,
                          child: const Text('Test Me Endpoint'),
                        ),
                        ElevatedButton(
                          onPressed: _token != null ? _testGetAvailableTasks : null,
                          child: const Text('Get Available Tasks'),
                        ),
                        ElevatedButton(
                          onPressed: _token != null ? _testListContentType : null,
                          child: const Text('List Content Type'),
                        ),
                      ],
                    ),
                    if (_apiResponse.isNotEmpty) ...[
                      const SizedBox(height: 12),
                      Container(
                        padding: const EdgeInsets.all(8),
                        decoration: BoxDecoration(
                          color: Colors.grey[100],
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: SingleChildScrollView(
                          scrollDirection: Axis.horizontal,
                          child: Text(
                            _apiResponse,
                            style: const TextStyle(
                              fontFamily: 'monospace',
                              fontSize: 12,
                            ),
                          ),
                        ),
                      ),
                    ],
                  ],
                ),
              ),
            ),

            // Token Info section
            if (_token != null) ...[
              const SizedBox(height: 16),
              Card(
                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
0
points
66
downloads

Publisher

verified publishercbsoft.ro

Weekly Downloads

Cross-platform OAuth package for Bapp authentication with Keycloak integration and API client

Homepage

License

unknown (license)

Dependencies

crypto, flutter, flutter_web_auth_2, http, shared_preferences, url_launcher

More

Packages that depend on bapp_auth