MaikuB / flutter_appauth

A Flutter wrapper for AppAuth iOS and Android SDKs
269 stars 239 forks source link

Example as a AuthService? #407

Closed idc77 closed 1 year ago

idc77 commented 1 year ago

That given example in this repo is probably allright for a single widget, but what if you need to access the state in multiple widgets?

I tried copying the code from the example but when I press "the button" nothing happens

import 'dart:convert';
import 'dart:io' show Platform;
import 'dart:math';
import 'package:flutter_appauth/flutter_appauth.dart';

class AuthService {
  bool _isBusy = false;
  final FlutterAppAuth _appAuth = const FlutterAppAuth();
  String? _codeVerifier;
  String? _nonce;
  String? _authorizationCode;
  String? _refreshToken;
  String? _accessToken;
  String? _idToken;

  String? _userInfo;

  final String _clientId = 'flutter-dev';
  final String _redirectUrl = 'de.icod.bicki:/oauthredirect';
  final String _issuer = 'https://connect.icod.de/auth/realms/bicki';
  final String _discoveryUrl =
      'https://connect.icod.de/auth/realms/bicki/.well-known/openid-configuration';
  final String _postLogoutRedirectUrl = 'de.icod.bicki:/';
  final List<String> _scopes = <String>[
    'openid',
    'profile',
    'email',
    'offline_access',
    'roles'
  ];

  final AuthorizationServiceConfiguration _serviceConfiguration =
      const AuthorizationServiceConfiguration(
    authorizationEndpoint: 'https://connect.icod.de/auth/realms/bicki/protocol/openid-connect/auth',
    tokenEndpoint: 'https://connect.icod.de/auth/realms/bicki/protocol/openid-connect/token',
    endSessionEndpoint: 'https://connect.icod.de/auth/realms/bicki/protocol/openid-connect/logout',
  );

  Future<void> endSession() async {
    try {
      _setBusyState();
      await _appAuth.endSession(EndSessionRequest(
          idTokenHint: _idToken,
          postLogoutRedirectUrl: _postLogoutRedirectUrl,
          serviceConfiguration: _serviceConfiguration));
      _clearSessionInfo();
    } catch (_) {}
    _clearBusyState();
  }

  void _clearSessionInfo() {
    _codeVerifier = null;
    _nonce = null;
    _authorizationCode = null;
    _accessToken = null;
    _idToken = null;
    _refreshToken = null;
    _userInfo = null;
  }

  Future<void> refresh() async {
    try {
      _setBusyState();
      final TokenResponse? result = await _appAuth.token(TokenRequest(
          _clientId, _redirectUrl,
          refreshToken: _refreshToken, issuer: _issuer, scopes: _scopes));
      _processTokenResponse(result);
    } catch (_) {
      _clearBusyState();
    }
  }

  Future<void> exchangeCode() async {
    try {
      _setBusyState();
      final TokenResponse? result = await _appAuth.token(TokenRequest(
          _clientId, _redirectUrl,
          authorizationCode: _authorizationCode,
          discoveryUrl: _discoveryUrl,
          codeVerifier: _codeVerifier,
          nonce: _nonce,
          scopes: _scopes));
      _processTokenResponse(result);
    } catch (_) {
      _clearBusyState();
    }
  }

  Future<void> signInWithNoCodeExchange() async {
    try {
      _setBusyState();
      // use the discovery endpoint to find the configuration
      final AuthorizationResponse? result = await _appAuth.authorize(
        AuthorizationRequest(_clientId, _redirectUrl,
            discoveryUrl: _discoveryUrl, scopes: _scopes, loginHint: 'bob'),
      );

      // or just use the issuer
      // var result = await _appAuth.authorize(
      //   AuthorizationRequest(
      //     _clientId,
      //     _redirectUrl,
      //     issuer: _issuer,
      //     scopes: _scopes,
      //   ),
      // );
      if (result != null) {
        _processAuthResponse(result);
      }
    } catch (_) {
      _clearBusyState();
    }
  }

  Future<void> signInWithNoCodeExchangeAndGeneratedNonce() async {
    try {
      _setBusyState();
      final Random random = Random.secure();
      final String nonce =
          base64Url.encode(List<int>.generate(16, (_) => random.nextInt(256)));
      // use the discovery endpoint to find the configuration
      final AuthorizationResponse? result = await _appAuth.authorize(
        AuthorizationRequest(_clientId, _redirectUrl,
            discoveryUrl: _discoveryUrl,
            scopes: _scopes,
            nonce: nonce),
      );

      if (result != null) {
        _processAuthResponse(result);
      }
    } catch (_) {
      _clearBusyState();
    }
  }

  Future<void> signInWithAutoCodeExchange(
      {bool preferEphemeralSession = false}) async {
    try {
      _setBusyState();

      // show that we can also explicitly specify the endpoints rather than getting from the details from the discovery document
      final AuthorizationTokenResponse? result =
          await _appAuth.authorizeAndExchangeCode(
        AuthorizationTokenRequest(
          _clientId,
          _redirectUrl,
          serviceConfiguration: _serviceConfiguration,
          scopes: _scopes,
          preferEphemeralSession: preferEphemeralSession,
        ),
      );

      // this code block demonstrates passing in values for the prompt parameter. in this case it prompts the user login even if they have already signed in. the list of supported values depends on the identity provider
      // final AuthorizationTokenResponse result = await _appAuth.authorizeAndExchangeCode(
      //   AuthorizationTokenRequest(_clientId, _redirectUrl,
      //       serviceConfiguration: _serviceConfiguration,
      //       scopes: _scopes,
      //       promptValues: ['login']),
      // );

      if (result != null) {
        _processAuthTokenResponse(result);
      }
    } catch (_) {
      _clearBusyState();
    }
  }

  void _clearBusyState() {
    _isBusy = false;
  }

  void _setBusyState() {
    _isBusy = true;
  }

  void _processAuthTokenResponse(AuthorizationTokenResponse response) {
    _accessToken = response.accessToken!;
    _idToken = response.idToken!;
    _refreshToken = response.refreshToken!;
  }

  void _processAuthResponse(AuthorizationResponse response) {
    // save the code verifier and nonce as it must be used when exchanging the token
    _codeVerifier = response.codeVerifier;
    _nonce = response.nonce;
    _authorizationCode = response.authorizationCode!;
    _isBusy = false;
  }

  void _processTokenResponse(TokenResponse? response) {
    _accessToken = response!.accessToken!;
    _idToken = response.idToken!;
    _refreshToken = response.refreshToken!;
  }

  String? get accessToken => _accessToken;
  String? get idToken => _idToken;
  String? get refreshToken => _refreshToken;
}

and

import 'dart:developer';

import 'package:bicki/services/auth/auth_service.dart';
import 'package:flutter/material.dart';

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

  @override
  State<SplashPage> createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  final AuthService _authService = AuthService();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: ListView(
          children: [
            TextButton(style: ButtonStyle(foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),),
              onPressed: () {
              _authService.signInWithAutoCodeExchange();
              if (_authService.idToken != null) {
                log(_authService.idToken!);
              }
              },
              child: const Text('TextButton'),
            ),
            TextButton(
                onPressed: () {},
                child: Text(_authService.idToken != null? _authService.idToken! : '')
            )
          ],
        )
      ),
    );
  }

}

As you can probably tell I'm quite new or rather returning to flutter after a few years break. Meanwhile much has changed and maybe it didn't, but I'm taking a wild guess that this auth will be needed in multiple widgets.

If y ou can't or won't help, can you point me to where I can get help?

I''m running this as an app on linux. Nothing happens when I press the "TextButton" button.

MaikuB commented 1 year ago

I''m running this as an app on linux

If you actually meant your app was targeting and running on Linux as opposed to your development environment being on Linux then the issue you're seeing would because the plugin doesn't support Linux. If this is indeed what's happening and you were debugging the app then believe you should've been getting an error (e.g. UnimplementedError()) or some exception being thrown. pub.dev should advertise the supported platforms

image

idc77 commented 1 year ago

Thanks for the response. I see how this can be misunderstood. Writing on Linux with the Linux target platform.

Well I tried this now (Android target) and it actually worked.

I come from JS frontend, Vue in the 2 recent years. What would you as an experienced Flutter guy recommend to use for global state?

I tried what I posted above and the 2nd Button would not update, so that means I need some reactive state management. How do YOU use your package in real world apps?

idc77 commented 1 year ago

Success. I made it work with Provider :) https://github.com/idc77/authskel

Still if you as the more experienced Flutter guy have a better approach, please share. Going to close it.