はじめに
前回の「(6)AWS Cognito 「パスワードを忘れた方はこちら」」までで、Cognitoを使ったログイン処理を一通り実装しました。今回は、サードパーティーによるログインをおこないたいと思います。サービスを利用しようと考えているユーザーはわざわざサービス専用にログインアカウントを作る必要がないですし、ID/PWD管理も一元化されて楽になるので利用開始の障壁を低くすることができます。
AWSのCognitoでは、Facebook,Amazon,Google,Appleといった主要な認証プロバイダーを標準でサポートしていますが、今回は、Googleを利用するパターンで開発していきたいと思います。
参考文献
公式サイト
amazon_cognito_identity_dart_2
- https://pub.dev/packages/amazon_cognito_identity_dart_2 > Usage > Use Case 19.
Googleの設定 その1
まずは、ID連携用にGoogle側の準備をします。Google Developer Consoleを開きあなたのGoogleアカウントでログインして登録を完了させてください。
認証情報 > __OAuth同意画面__を選択します。__アプリケーション名__を入力し__作成ボタン__を押下します。
認証情報 > 認証情報を作成 > OAuthクライアントID を選択します。今回利用するGoogleでのログインではOpenID Connect
というOAuth2
を拡張したプロトコルに基づいています。ここで発行するOAuthのクライアントはCognitoからGoogleを呼び出すときに利用するIDになりますので、後ほどCognito側に登録することになります。
__アプリケーションの種類__で__ウェブアプリケーション__を選択し、任意の__名前__を入力し、__作成ボタン__を押下します。後ほど承認済みのリダイレクトURIを入力するのですが、このタイミングではまだ決まっていなので未入力で進めます。
作製が完了すると以下の画面が表示されます。クライアントIDとクライアントシークレットをCognitoに設定する必要があるので控えてください。
Cognitoの設定
いったんAWS Cognito側の設定に移動します。マネージメントコンソールよりCognitoのユーザープールを開いてください。フェデレーション > __IDプロバイダー__を開き、__Google__を選択します。先ほどGoogleで作成したクライアントIDを__Google アプリ ID__に、クライアントシークレットを__アプリシークレット__に入力します。また、__承認スコープ__に__profile email openid__を入力して、Google有効化ボタン
を押下します。
次に、アプリの統合 > __アプリクライアントの設定__を開きます。__有効なIDプロバイダ__で__Google__をチェック、サインインとサインアウトの URL > __コールバック URL__に__myapp://__を入力、OAuth 2.0 > 許可されている OAuth フロー で__Authorization code grant__をチェック、許可されている OAuth スコープで__email__と__openid__と__aws.cognito.signin.user.admin__と__profile__をチェックし__変更の保存ボタン__を押下します。
次に、アプリの統合 > __ドメイン名__を開きます。今回は、__Amazon Cognito ドメイン__を使うパターンで進めます。__ドメインのプレフィックス__にお好きなドメイン名を設定し、__使用可能かチェックするボタン__を押下します。
なお、「AWSのみで構築する独自ドメイン/SSL/WordPressサイト」で紹介したような形で独自ドメインを取得しAWSで証明書を発行していれば、__自分が所有するドメイン__側もすぐ設定できます。このドメインは利用できます
と表示されたら、__変更の保存ボタン__を押下します。ここで作成したURLをGoogle側に登録するので控えておいてください。
Googleの設定 その2
Cognitoの設定が完了したら、再度Googleの設定に戻ります。認証情報 > __OAuth 2.0 クライアント ID__から先ほど作成したクライアントを開きます。承認済みのリダイレクト URI__にCognitoで設定したURLに/oauth2/idpresponse__を付与した、
「https://[入力したドメイン].auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse」を入力して__保存ボタン__を押下してください。
モバイルアプリへの組み込み
MaterialAppのroutesにGoogleでログイン画面を追加
__'/LoginByGoogle'__という名前で定義を追加しました。
routes: <String, WidgetBuilder>{
'/': (_) => new MyHomePage(),
'/TopPage': (_) => new TopPage(),
'/RegisterUser': (_) => new RegisterUserPage(),
'/ConfirmRegistration': (_) => new ConfirmRegistration(null),
'/ForgotPassword': (_) => new ForgotPassword(),
'/LoginByGoogle': (_) => new LoginByGoogle(),
},
ログイン画面の変更
Googleでログインボタンを一番下に追加しています。押下するとGoogleでログイン画面に飛びます。またまたデザインは後回しです。
Divider(color: Colors.black),
RaisedButton(
child: Text('Googleでログイン'),
color: Colors.indigo,
textColor: Colors.white,
shape: StadiumBorder(),
onPressed: () =>
Navigator.of(context).pushNamed('/LoginByGoogle'),
),
Googleでログイン画面
WebViewのコンポーネントを使ってGoogleのログイン画面を表示します。WebViewのコンポーネント自体はいくつか種類がありますが、Flutterの公式を利用しました。SDKのサンプルコードもそうですし、長い目で見れば公式というのは安心感があります。その他、必要なライブラリを一通りimportをします。
import 'dart:async';
import 'dart:convert';
import 'dart:core';
import 'package:http/http.dart' as http;
import 'package:webview_flutter/webview_flutter.dart';
次に画面です。__WebView__を使い、initialUrl
にCognitoの認証エンドポイントとして開くよう指定します。Cognitoユーザープールの認証エンドポイントURLにはCognitoのユーザープールID(COGNITO_POOL_URLに格納)、クライアントのID(COGNITO_CLIENT_IDに格納)の値が必要になります。なお、Googleで発行したクライアントではなく、Cognitoで発行したクライアントになりますのでご注意ください。
次に、useragent
には正規のデバイスが送信する文字列と同じ値を設定します(SDKサンプルからのコピペですが、LG Nexus 5の設定値らしいです)。これを設定しないとGoogleが正しくログイン画面を表示してくれず、「変なデバイスでアクセスするな!」といった感じでエラーが表示されます。
最後に、navigationDelegate
に画面遷移した時の処理を書きます。ざっくりとCognito認証エンドポイント → Googleログイン画面 → 端末(myapp://
)といった画面遷移をします。myapp://
に戻ってきたときにGoogleから渡されるAuthoraization Code
がパラメータとして入るので、myapp://
が開いたとき、かつ、クエリパラメータにcode
を持つときという条件で次の処理に進むようにしています。SDKのサンプルではこの辺の実装が適当なのできちんと__Uriクラス__を使ってUrlをパースして処理するように書き換えています。
myapp://
でコードを取得した後は、_signUserInWithAuthCodeメソッドで認証セッションを取得し、__Navigator.of(context).pushReplacementNamed('/TopPage');__でログイン結果画面に飛びます。この画面は(5)AWS Cognitoでログインで作成した画面と共通です。
_signUserInWithAuthCodeメソッドでは、Cognitoユーザープールのトークンエンドポイントにリクエストを投げてセッションを確立します。トークンエンドポイントURLには、先ほどと同様CognitoのユーザープールID、クライアントのIDの値と、Googleから取得したAuthoraization Code
が必要になります。トークンエンドポイントを呼び出した結果を使い、__CognitoUserSession__をインスタンス化します。これでCognitoでログインした場合と同様の状態になります。
class LoginByGoogle extends StatelessWidget {
final Completer<WebViewController> _webViewController =
Completer<WebViewController>();
@override
Widget build(BuildContext context) {
// Googleのログイン画面を毎回出してテストするためにCookieをクリア
CookieManager().clearCookies();
return Scaffold(
appBar: AppBar(
title: Text('Googleでログイン'),
),
body: Center(
child: WebView(
initialUrl: "https://$COGNITO_POOL_URL" +
".amazoncognito.com/oauth2/authorize?identity_provider=Google&redirect_uri=myapp://&response_type=CODE&client_id=$COGNITO_CLIENT_ID" +
"&scope=email+openid+profile+aws.cognito.signin.user.admin",
userAgent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_webViewController.complete(webViewController);
},
navigationDelegate: (NavigationRequest request) async {
if (request.url.startsWith("myapp://")) {
var uri = Uri.parse(request.url);
if (uri.queryParameters.containsKey('code')) {
try {
session = await _signUserInWithAuthCode(
uri.queryParameters['code']);
Navigator.of(context).pushReplacementNamed('/TopPage');
} catch (e) {
print(e);
}
}
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
gestureNavigationEnabled: true,
)));
}
Future _signUserInWithAuthCode(String authCode) async {
String url = "https://$COGNITO_POOL_URL" +
".amazoncognito.com/oauth2/token?grant_type=authorization_code&client_id=" +
"$COGNITO_CLIENT_ID&code=" +
authCode +
"&redirect_uri=myapp://";
final response = await http.post(url,
body: {},
headers: {'Content-Type': 'application/x-www-form-urlencoded'});
if (response.statusCode != 200) {
throw Exception("Received bad status code from Cognito for auth code:" +
response.statusCode.toString() +
"; body: " +
response.body);
}
final tokenData = json.decode(response.body);
final idToken = new CognitoIdToken(tokenData['id_token']);
final accessToken = new CognitoAccessToken(tokenData['access_token']);
final refreshToken = new CognitoRefreshToken(tokenData['refresh_token']);
return new CognitoUserSession(idToken, accessToken,
refreshToken: refreshToken);
}
}
実装した画面は以下になります。Cognitoの認証エンドポイントは実際に画面を表示せずGoogleログイン画面にリダイレクトされるので、Googleのログイン画面が表示されます。
まとめ
Googleでログインし、フェデレーションすることができました。Cognitoは他にも色々なサードパーティーのIdPをサポートしています。他のプロバイダでのログインも同じような流れで導入できると思います。繰り返しますが、エンドユーザー的にはあれこれ考えず、すでに使っているサービスのIDとパスワードでログインするだけで新しいサービスが使い始められます。「IDを登録する」というサービス導入の障壁を取り除けるので積極的にサポートしていくのが良いかと思います。次回は「(8)Cognitoの認証情報を使ってAPIを呼び出す」とし、認証した結果をもとに実際のサービスのAPIをたたくところの処理を見ていきたいと思います。