質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.34%

flutterでflutter_appauthプラグインを使ってoauthログインを実装したい3

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 671

sagme

score 9

前提・実現したいこと

前回の質問 flutterでflutter_appauthプラグインを使ってoauthログインを実装したい2 からの続きの質問です。

アプリから、認証サーバのログインページを呼び出してログインし、認証が済んだら
アプリに戻ってアクセストークンでAPIを取得しようとしています。

認証サーバは自前で用意したものを使います。(FirebaseやSNS認証などは使わない)
今回はKeycloakを使い、DockerのコンテナでローカルPCに立ち上げています。
そのため、以下のプラグインを使用して実装しようとしています。

flutter_appauth 0.8.2

こちらのGitHubのサンプルコードを参照して実装しました。
MaikuB/flutter_appauth

発生している問題・エラーメッセージ

困っていることその①
アプリから、認証サーバのログインページを呼び出してログインし、認証済みになるところまでは動作するのですが
callback画面からアプリに制御が戻らず、エラーになっています。

① アプリ初期画面
イメージ説明

② 認証サーバのログイン画面
イメージ説明

③ ログイン画面にユーザID,PWを入力
イメージ説明

④ 認証成功した後のcallback画面(戻るリンクは機能していない)
イメージ説明

⑤ ↑の画面の×印をタップして画面を閉じてアプリに戻った所
イメージ説明

⑥エラーメッセージ(④の画面を手動で閉じたので、ソースコード中のresultに何も入らずエラーになった
エラー後もアプリは動いているが、その後の画面遷移はしていない
イメージ説明

Launching lib/main.dart on Android SDK built for x86 64 in debug mode...
✓ Built build/app/outputs/apk/debug/app-debug.apk.
I/flutter ( 5095): 1
I/flutter ( 5095): 2
I/flutter ( 5095): e=PlatformException(authorize_and_exchange_code_failed, Failed to authorize: [error: null, description: User cancelled flow], null)

該当のソースコード

main.dart

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:flutter_auth_login_app/screen1.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final String _clientId = 'test001';
  final String _redirectUrl = 'http://10.0.2.2:10081/…/callback.php' ;  // callbackのURL

  final List<String> _scopes = <String>[
    'openid',
    'offline_access',
  ];

//  String _codeVerifier;
//  String _authorizationCode;
  String _accessToken;
  String _refreshToken;
  String _userInfo = '';

  final FlutterAppAuth _appAuth = FlutterAppAuth();
//  final TextEditingController _authorizationCodeTextController = TextEditingController();
  final TextEditingController _idTokenTextController = TextEditingController();
  final TextEditingController _accessTokenExpirationTextController = TextEditingController();
  final TextEditingController _accessTokenTextController = TextEditingController();
  final TextEditingController _refreshTokenTextController = TextEditingController();

  final AuthorizationServiceConfiguration _serviceConfiguration =
    AuthorizationServiceConfiguration(
      'http://10.0.2.2:18080/…/auth',  // 認証エンドポイントのURL
      'http://10.0.2.2:18080/…/token'  // トークンエンドポイントURL
    );

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

  void _loginFunc() async {
    // 認証とアクセス、ID、リフレッシュトークン取得を一度にやる場合
    print('1');
    try {
      print('2');   // ここまで流れて↓のresultに値が戻らないため、エラーになる
      final AuthorizationTokenResponse result =
          await _appAuth.authorizeAndExchangeCode(
        AuthorizationTokenRequest(
          _clientId,
          _redirectUrl,
          serviceConfiguration: _serviceConfiguration,
          scopes: _scopes),
      );

      print('3');
      // アクセス、ID、リフレッシュトークン格納
      if (result != null) {
        _accessToken = _accessTokenTextController.text = result.accessToken;
        _idTokenTextController.text = result.idToken;
        _refreshToken = _refreshTokenTextController.text = result.refreshToken;
        _accessTokenExpirationTextController.text = result.accessTokenExpirationDateTime?.toIso8601String();
      }

      // API取得
      print('4=$result');
      if (result != null) {
        print('4 in');
        await _testApi(result);
      }

      // 画面遷移
      print('5');
      _gotoScreen1();

    // エラー処理
    } catch(e) {
      print('e=$e'); //エラー時のメッセージ出力
    }
  }

  // API取得
  Future<void> _testApi(TokenResponse response) async {
    final http.Response httpResponse = await http.get(
        'http://10.0.2.2:18082/…/data',  // APIのエンドポイント
        headers: <String, String>{'Authorization': 'Bearer $_accessToken'});
    setState(() {
      _userInfo = httpResponse.statusCode == 200 ? httpResponse.body : '';
    });
  }

  // 画面遷移 screen1 API取得後に遷移する画面
  void _gotoScreen1() {
    Navigator.pushReplacement(
      context,
      new MaterialPageRoute<Null>(
        settings: const RouteSettings(name: "/screen1"),
        builder: (BuildContext context) => Screen1(api:_userInfo),
      ),
    );
  }

  // widget
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(title: Text(widget.title),),
      body: Center(

        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[

            Padding(
              padding: EdgeInsets.only(bottom: 40),
              child: Text('アプリ初期画面'),
            ),

            FlatButton(key:null, onPressed: _loginFunc,
              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(3.0)),
              color: Color(0xFF4c6cb3),             // 群青色
              child: Text('ログインページへ',),
            )
          ],
        ),
      ),
    );
  }
}
screen1.dart

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_appauth/flutter_appauth.dart';

//// widget ////
class Screen1 extends StatefulWidget {
  String api;

  Screen1({Key key, this.api}): super(key: key);

  @override
  _Screen1State createState() => _Screen1State(this.api);
}

class _Screen1State extends State<Screen1> {
  String _api;

    _Screen1State(String api){
    this._api = api;
  }

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

    @override
  Widget build(BuildContext context) {

    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(title: Text('screen1'),),
      body: Column(
        children: [
          Text('screen1'),
          Text('$_api'),
        ]
      )
    );
  }
}
callback.php

  // webアプリの名残でphpファイルに書いてあるが、php部分は無し。戻るリンクの中身は無い。
  認証成功<br>
  <a href="">戻る</a>

試したこと

なぜ引数を渡してもresultが返ってこないのかを調べるため
_loginFunc()の認証する部分↓の中で

final AuthorizationTokenResponse result =
          await _appAuth.authorizeAndExchangeCode(
        AuthorizationTokenRequest(
          _clientId,      // php-test
          _redirectUrl,
          serviceConfiguration: _serviceConfiguration, 
          scopes: _scopes),
      );


何が行われているのか確認しようとしましたが、どうやって確認したら良いか分からない状態です。

困っていることその②

参照したGitのサンプルコード MaikuB/flutter_appauth の中には、flutter_appauth だけでなく
flutter_appauth_platform_interfaceの実装も含まれていましたが、この部分がどういう役割をしているのか、flutter_appauth の main.dart からどのように参照されて使われているのかが分かりません。

そのため、自分の書いた上記のコードにはflutter_appauth_platform_interfaceの実装が含まれていないので
これが原因なのかなと思いますが、使い方が分からず困っています。

補足情報(FW/ツールのバージョンなど)

macOS Mojave
Flutter 1.12.13+hotfix.5
Tools • Dart 2.7.0

説明が長くなってしまいましたが、どうぞよろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

0

追記:
解決はしていないのですが、色々試したところ現象が変化し
困っている内容が変わったため、こちらの質問を一旦クローズし、新たに質問を立て直させて頂きます

○現在の状況
困っていることその①について
認証後にアプリに戻ってこない → 戻ってくるようにはなったが、直後にアプリが落ちる(改めて別の質問を立てます)。
アプリに戻ってくる部分の解決方法は、前の質問
flutterでflutter_appauthプラグインを使ってoauthログインを実装したい2 の自己解決の追記に書きました。

困っていることその②
サンプルコードのflutter_appauth_platform_interfaceの実装がなぜあるのか分からない。
flutter_appauth_platform_interfaceは、flutter_appauthから更に呼ばれているプラグインなので
実装部分は書かなくても動く(自分のアプリはそう)のではないかと思うのですが
なぜこのサンプルコードに実装部分が含まれているのかが分からない。

まだ分からない部分について、解決方法ご存知の方がいれば、引き続きご意見をお待ちしています。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る