昨年から学校でiOSアプリを作っていましたが、
そのときにAWS Mobile SDK for iOS と Amazon Cognito のユーザープール を使って
サインアップやサインインなどの認証機能を作りました。
日本語の情報が少なかったり、Objective-Cで書かれていたり、 公式のリファレンスが読みにくかったりと調査しづらかったので、自分でまとめてみました。
Amazon Cognito とは?
Amazon Cognito とは、 AWSが提供している、ウェブアプリケーションやモバイルアプリ(iOS/Androidなど)での サインイン機能をサポートするサービスです。
その中でユーザープールはサインインするユーザー・アカウントを管理するためのディレクトリを提供します。
iOSアプリ(Swift/Objective-C)、Androidアプリ(Java)、React Native、ウェブアプリ(JavaScript)向けにSDKが用意されており、 それを使うことでユーザ認証機能を簡単に作ることができます。
サンプルアプリ
自分が開発に関わっていたiOSアプリを元に Cognito・ユーザープールを取り入れた サンプルのアプリを作りました。
サンプルアプリでは以下の機能を実装しています。
- サインイン
- サインアップ(ユーザー名、メールアドレス、パスワード)
- サインアップ後のメールアドレスによる確認コード認証
- サインインしている時のユーザー名の表示
また、awslabs からもサンプルのアプリが出されており、自分もこちらを参考にしました。
上記以外の機能も実装されています。
GitHub: awslabs/aws-sdk-ios-samples
CognitoYourUserPoolsSample(Swift)
アプリの実装に困った時には参考にしてみると良いと思います。
実装の仕方
iOSアプリに認証機能を作るときの流れは以下の通りです。
手順
- AWSのアカウントを作成
- サインインして Cognito を開く
- ユーザープールを作成
- アプリクライアントを作成
- アプリを作成
- ライブラリ(AWSCognitoIdentityProvider)のインストール
- ユーザープールの情報をアプリに記述
- 認証処理・認証画面を実装する
AWSアカウントの作成
EC2、Lambdaなど別のAWSサービスを使っている場合などはアカウントを持っていると思います。
持っていない場合はアカウントを作る必要があります。
AWS公式でアカウントの作り方(AWSアカウント作成の流れ) が紹介されていますのでこちらを参考にするのが良いと思います。
サインインして Cognito を開く
サインインした後、Cognitoのページを開きます。 マネジメントコンソールからは上部の「サービス」->「セキュリティ、ID、およびコンプライアンス」->「Cognito」で 開くことができます。 検索ボックスに「Cognito」と入力しても出ます。
ユーザープールを作成
Cognito の画面に入れたら、「ユーザープールの管理」を開き、右上の「ユーザープールを作成する」を開きます。
プール名の欄に好きなプール名を入力します。
「デフォルトを確認する」「ステップに従って設定する」のいずれかをクリックします。
「デフォルトを確認する」を選んだ場合は以下の画面が表示されるので、ここで認証に必要な各属性を設定します。
メールアドレスでのサインインを許可するかどうか、パスワードの文字数やポリシー、MFA(多要素認証)などの設定ができます。
作成した後から設定を変更することもできます。
設定ができたら「プールの作成」をクリックしてユーザープールを作成します。
作成後「プールID」が表示されるのでメモしておきます。こちらはiOSアプリのプログラムに記載する必要があります。
アプリクライアントを作成
メモした後、左側の「全般設定」->「アプリクライアント」を開きます。
下記の画面が表示されるので各項目を確認し、よければ「アプリクライアントの作成」をクリックします。
ここでiOSアプリからユーザープールにアクセスするために必要なIDとシークレットキーが作成されます。
デフォルトでは「ユーザー名パスワードベースの認証を有効にする」のチェックが入っていませんが、
サインイン時にパスワードでの認証のみで十分だという場合はこの項目にチェックをいれます。
作成に成功すると、アプリクライアントID、アプリクライアントのシークレットが表示されます。
後でiOSアプリのプログラムにこれらを記載する必要があるのでメモしておきましょう。
アプリを作成
ユーザープール・アプリクライアントを作成した後は、iOSアプリの作成に移ります。
XCode から新規iOSアプリ用のXCode Projectを作成します。
複数画面(Scene)を作ることになりますが、テンプレートは「Single View App」を選び、
後で Storyboard から画面を増やすのが良いと思います。
以下のようなサイトが参考になると思います。
ライブラリ(AWSCognitoIdentityProvider)のインストール
作成したアプリで Cognito の認証機能を動かすには iOS用のSDK AWSCognitoIdentityProviderが必要です。 こちらをインストールします。
ライブラリ管理には Cocoapods というツールを使用します。
以下のコマンドで gem を利用して Cocoapods をインストールします。
sudo gem install cocoapods
Podfile の設定をした後、プロジェクトルートで以下のコマンドを実行します。
pod install
なお、インストール後は [アプリ名].xcworkspace
というファイルが生成されるので、
いったんプロジェクトを終了してから [アプリ名].xcworkspace
からプロジェクトを開いてください。
今後も [アプリ名].xcodeproj
ではなく [アプリ名].xcworkspace
から開きます。
ユーザープールの情報をアプリに記述
ユーザープールの各情報を Constants.swift に定数としてまとめているので、 そこにユーザープールに表示されている情報を記述します。
import AWSCognitoIdentityProvider
struct CognitoConstants {
// UserPool の各情報をもとにそれぞれの値を設定する.
/// ユーザープールを設定しているリージョン.
static let IdentityUserPoolRegion: AWSRegionType = .Unknown
/// ユーザープールID.
static let IdentityUserPoolId: String = "YourIdentityUserPoolId"
/// アプリクライアントID.
static let AppClientId: String = "YourAppClientId"
/// アプリクライアントのシークレットキー.
static let AppClientSecret: String? = nil
/// プロバイダキー. "UserPool" で良さそう.
static let SignInProviderKey: String = "UserPool"
/// インスタンス生成禁止.
private init() {}
}
AppDelegate.swift
に以下のようにコードを追加します。
これによってアプリ起動時にConstants.swift
の情報に従ってユーザープールの設定を行ってくれます。
import AWSCognitoIdentityProvider
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// AWS サービス設定を作成.
let serviceConfiguration: AWSServiceConfiguration = AWSServiceConfiguration(
region: CognitoConstants.IdentityUserPoolRegion,
credentialsProvider: nil
)
// ユーザプール設定を作成.
let userPoolConfigration: AWSCognitoIdentityUserPoolConfiguration =
AWSCognitoIdentityUserPoolConfiguration(clientId: CognitoConstants.AppClientId,
clientSecret: CognitoConstants.AppClientSecret,
poolId: CognitoConstants.IdentityUserPoolId)
// ユーザープールクライアントを初期化.
AWSCognitoIdentityUserPool.register(with: serviceConfiguration,
userPoolConfiguration: userPoolConfigration,
forKey: CognitoConstants.SignInProviderKey)
return true
}
...(略)...
}
認証処理・認証画面を実装する
認証画面を Storyboard などに、認証処理を ViewController などに追加します。
サンプルアプリでは 以下のように ViewController、Storyboard を作っています。
- Storyboard
Main.storyboard - Main View Controller Scene - 起動画面(LaunchScreen)の後に最初に表示される画面のUI - サインインしているときはそのユーザー名が表示される - サインイン Scene - サインイン画面のUI - 新規アカウント Scene - サインアップ画面のUI - 確認コード Scene - 確認コード入力画面のUI
- MainViewController.swift - 起動画面の後に最初に表示される画面の処理 - SignInViewController.swift - サインイン時の処理 - サインイン Scene に対応 - SignUpViewController.swift - サインアップ時の処理 - 新規アカウント Scene に対応 - ConfirmationViewController.swift - 確認コード認証時の処理 - 確認コード Scene に対応
サインイン、サインアップ、確認コードの処理を行うコードをこちらでも紹介します。
サインイン処理 - SignInViewController.swift
/// サインインする.
@IBAction func signIn(_ sender: UIButton) {
guard let username: String = self.usernameField.text,
let password: String = self.passwordField.text else {
self.presentErrorAlert(title: "ユーザ名またはパスワードが入力されていません。", message: nil)
return
}
self.indicatorView.startAnimating()
sender.isEnabled = false
let pool: AWSCognitoIdentityUserPool = AWSCognitoIdentityUserPool(forKey: CognitoConstants.SignInProviderKey)
let user: AWSCognitoIdentityUser = pool.getUser(username)
user.getSession(username, password: password, validationData: nil).continueWith { task in
DispatchQueue.main.async {
self.indicatorView.stopAnimating()
sender.isEnabled = true
if let error: NSError = task.error as NSError? {
self.presentErrorAlert(title: error.userInfo["__type"] as? String,
message: error.userInfo["message"] as? String)
} else {
self.dismiss(animated: true, completion: nil)
}
}
return task
}
}
サインアップ処理 - SignUpViewController.swift
/// サインアップする.
@IBAction func signUp(_ sender: UIButton) {
guard let username: String = self.usernameField.text,
let email: String = self.emailField.text,
let password: String = self.passwordField.text else {
print("Missing username, email or password.")
self.presentErrorAlert(title: "ユーザ名、メールアドレスまたはパスワードが入力されていません。", message: nil)
return
}
self.indicatorView.startAnimating()
sender.isEnabled = false
let name: AWSCognitoIdentityUserAttributeType = AWSCognitoIdentityUserAttributeType(name: "name", value: username)
let emailAttribute: AWSCognitoIdentityUserAttributeType = AWSCognitoIdentityUserAttributeType(name: "email", value: email)
let attributes: [AWSCognitoIdentityUserAttributeType] = [name, emailAttribute]
let pool: AWSCognitoIdentityUserPool = AWSCognitoIdentityUserPool(forKey: CognitoConstants.SignInProviderKey)
pool.signUp(username, password: password, userAttributes: attributes, validationData: nil).continueWith { task in
DispatchQueue.main.async {
if let error: NSError = task.error as NSError? {
self.presentErrorAlert(title: error.userInfo["__type"] as? String,
message: error.userInfo["message"] as? String)
self.indicatorView.stopAnimating()
sender.isEnabled = true
} else {
if let result: AWSCognitoIdentityUserPoolSignUpResponse = task.result {
self.indicatorView.stopAnimating()
sender.isEnabled = true
// ユーザがメールやSMSでの認証を必要とするかどうかで処理を分ける.
if (result.user.confirmedStatus != .confirmed) {
self.performSegue(withIdentifier: "ConfirmSignUp", sender: [username,
result.codeDeliveryDetails?.destination])
} else {
// 確認コード認証が不要な場合は NavigationView を閉じる.
self.dismiss(animated: true, completion: nil)
}
}
}
}
return task
}
}
確認コード認証処理 - ConfirmationViewController.swift
/// 確認コードで認証する.
@IBAction func confirm(sender: UIButton) {
guard let code: String = self.confirmationCodeField.text else {
print("Missing confirmation code.")
self.presentErrorAlert(title: "確認コードが入力されていません。", message: nil)
return
}
if self.username != nil {
self.indicatorView.startAnimating()
sender.isEnabled = false
DispatchQueue.global(qos: .userInteractive).async {
let pool: AWSCognitoIdentityUserPool = AWSCognitoIdentityUserPool(forKey: CognitoConstants.SignInProviderKey)
let user: AWSCognitoIdentityUser = pool.getUser(self.username!)
user.confirmSignUp(code).continueWith { task in
if let error: NSError = task.error as NSError? {
DispatchQueue.main.async {
self.presentErrorAlert(title: "確認コードでの認証ができませんでした。",
message: error.userInfo["message"] as? String)
self.indicatorView.stopAnimating()
sender.isEnabled = true
}
} else {
DispatchQueue.main.async {
self.indicatorView.stopAnimating()
sender.isEnabled = true
self.dismiss(animated: true, completion: nil)
}
}
return task
}
}
}
}
0 件のコメント:
コメントを投稿