はじめに
iOSアプリ開発の学習で、FirebaseAuthenticationを使用してログイン機能を実装しました。
実装した機能の一部抜粋ではありますが、備忘録的に残しておきます。
この記事に書いたこと
- createUserメソッドを使用したユーザー登録機能
- signInメソッドを使用したユーザーログイン機能
- sendEmailVerificationメソッドを使用した確認メール送信機能
※Firebaseの導入については本記事では触れません。
実装
UserAuthManager
ユーザーの認証を受け持つクラスを作成します。
外部サービスのimportをあちこちに記述するのはあまりよろしくないので、
import FirebaseAuth
等の記述はこのファイルのみで完結できるように設計します。
import Foundation
import FirebaseAuth
import Firebase
final class UserAuthManager {
static let shared: UserAuthManager = .init()
private init(){}
// ~~~~~ 処理を実装 ~~~~~
}
UserAuthError(エラー型の用意)
サーバーからエラーが返ってくるケースに備えて作成しておきます。
enum UserAuthError: Error, LocalizedError {
case createUserError(errorMessage: String)
case userLoginError(errorMessage: String)
case sendEmailVerificationError(errorMessage: String)
case checkEmailVerification(errorMessage: String)
var errorDescription: String? {
switch self {
case .createUserError(let message):
return "ユーザー作成エラー:\(message)"
case .userLoginError(let message):
return "ログインエラー:\(message)"
case .sendEmailVerificationError(let message):
return "確認メール送信エラー:\(message)"
case .checkEmailVerification(let message):
return "メールアドレス検証エラー:\(message)"
}
}
}
createUser(ユーザーの新規作成)
func createUser(email: String, password: String, completion: @escaping (Result<Bool, UserAuthError>) -> Void) {
Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
if let error = error {
completion(.failure(UserAuthError.createUserError(errorMessage: error.localizedDescription)))
return
}
guard authResult != nil else {
completion(.failure(UserAuthError.createUserError(errorMessage: "入力された情報では登録できませんでした")))
return
}
completion(.success(true))
}
}
Result
クロージャーでResult<Bool, UserAuthError>
を返していますが、これはユーザー登録出来たかどうか以外特に渡したいパラメータも無かったので成功時Bool型にしています。
ただ失敗時は基本的にError型が返されるのでfalseが使われることがないのはちょっと気になっているところです。
もっと良い書き方がありそう。
error
サーバーから返ってきた結果について何パターンか処理を分けます。
-
errorが返ってきた場合(errorがnilでない)
この場合はResultでUserAuthErrorを返します。この時、呼び出し元にエラーの原因(通信状況?アドレス被り?など)も知らせる必要があるのでUserAuthError.createUserError(errorMessage: error.localizedDescription)
としてサーバーから受け取ったエラーメッセージも付けて返します。 -
errorは無いがauthResultも無い場合
errorが返ってきてないのにも関わらずauthResultが無い場合は、サーバー側も想定外のエラーとなるので、アプリ側でエラーメッセージを用意してUserAuthErrorを返します。 -
エラーもなくauthResultがある場合
登録成功しているので.success(true)
を返します。
signIn(ログイン)
func login(email: String, password: String, completion: @escaping (Result<Bool, UserAuthError>) -> Void) {
Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
if let error = error {
completion(.failure(UserAuthError.userLoginError(errorMessage: error.localizedDescription)))
}
guard authResult != nil else {
completion(.failure(UserAuthError.userLoginError(errorMessage: "入力された情報ではログインできませんでした")))
return
}
completion(.success(true))
}
}
やっていることはcreateUserとほぼ同じです。
sendEmailVerification(メールアドレスの確認)
不正なメールアドレスの使用を防ぐため、登録したメールアドレスに確認用のメールを送信する機能です。
受けとったメールアドレスのリンクをタップすることで、currentUser.isEmailVerifiedというパラメータがfalseからtrueに変わります。trueであれば確認済みの状態ということになります。
func sendEmailVerification(completion: @escaping (Result<Bool, UserAuthError>) -> Void) {
guard let user = Auth.auth().currentUser else { return }
user.sendEmailVerification { error in
if let error = error {
completion(.failure(UserAuthError.sendEmailVerificationError(errorMessage: error.localizedDescription)))
}
else {
completion(.success(true))
}
}
}
func checkEmailVerificationResult(completion: @escaping (Result<Bool, UserAuthError>) -> Void) {
guard let user = Auth.auth().currentUser else { return }
user.reload { error in
if let error = error {
completion(.failure(UserAuthError.checkEmailVerification(errorMessage: error.localizedDescription)))
}
else {
completion(.success(user.isEmailVerified))
}
}
}
関数を2つ用意しています。
sendEmailVerification()
はcurrentuserのメールアドレスに確認用のメールを送信するためのものです。
エラーなく送信完了の場合はtrue
を返し、エラーがある場合はUserAuthError
を返します。
checkEmailVerificationResult()
はメールアドレスの確認が終了しているか否かをチェックするためのものです。
エラーが返された場合(チェック自体が出来なかった場合)はUserAuthError
を返し、エラーがなければisEmailVerified
のBool値を返します。
補足
sendEmailVerification
はcurrentUserが持つメソッドなので、登録完了したユーザーに対して行うことになるという点に注意
Routing(おまけ)
アプリの起動経路の道中でユーザのログイン状態によって画面遷移先を変えます。
細かいところは省略していますが、userLoginCheck
という関数を用意してcurrentuser
を取得してログイン状態のチェック、及びメール確認の有無を見ています。
// Routerなどの画面遷移処理を受け持つクラス
func showRoot() {
UserAuthManager.shared.userLoginCheck { result in
switch result {
case .success(let user): // ログインしている場合
if user.emailVerified {
// さらにメールの確認も済んでいる場合は、ログイン後のトップページ等へ
}
else {
// メールの確認が済んでいない場合は、メールの確認処理を受け持つ画面へ
}
case .failure: // ログインしていない場合
// ログイン・新規登録の画面へ
}
}
}
// UserAuthManagerクラスに関数を用意
func userLoginCheck(completion: @escaping (Result<User, UserAuthError>) -> Void) {
let user = Auth.auth().currentUser
guard let userEmail = user?.email,
let userEmailVerified = user?.isEmailVerified else {
return completion(.failure(UserAuthError.loginFailure))
}
completion(.success(User(emailAddress: userEmail, emailVerified: userEmailVerified)))
}
終わりに
このようにユーザー認証用のクラスを用意しておくことで、ViewからUserAuthManager.shared.hogehoge { クロージャー }
という形で呼び出せます。
FirebaseAuthは個人アプリ開発などでもお世話になると思いますので、今後さらに理解を深めていければと思います。