Edited at

[Swift] macOSでFirebaseを使う ~ Authentication編

前回の続きです


Firebaseによる認証


Keychain

Firebaseの認証機構はKeychainを利用するように出来ているので、プログラマがあの面倒くさい実装をする必要がありません。

すごくありがたいですね!


仕様


  1. 認証が出来るようにする

  2. ユーザーを作成できるようにする

  3. サインアウトもできるようにする

  4. メインウインドウと、認証/ユーザー作成パネルをもつ

  5. メインウインドウはサインアウトボタンのみ

  6. 未認証であれば認証/ユーザー作成パネルを表示し、メインウインドウは表示しない

  7. 認証済みであればメインウインドウを表示し、認証/ユーザー作成パネルは表示しない

  8. ユーザーへのアラートなどは実装しない。stdoutにエラーログを出力。

よくある感じの奴です。


Authenticationを実装

ではサクサクっと実装していきます。


メインウインドウ

まずはメインウインドウを作成します。

xib付きでNSWindowControllerのサブクラスとしてMainWindowController.swiftを作成します。

MainWindowController.swiftはNibをロードするだけでいいので以下のようにします。


MainWindowController.swift

import Cocoa

class MainWindowController: NSWindowController {

var nibName: NSNib.Name {

return NSNib.Name("MainWindowController")
}
}


NSWindowController.xibはウインドウに「サインアウト」ボタンを貼り付けてFirstRespondersignOut:アクションを作って接続します。

この後 signOut(_:)メソッドを作りますのでアクションの接続はそのあとでも構いません。

NSWindowのVisible At Launchはオフにします。


認証/ユーザー作成パネル

メインウインドウと同じようにxib付きでNSWindowControllerのサブクラス LogInPanelController.swiftを作成します。

UIパーツとのデータのやり取りにはいろいろな方法がありますが、今回はCocoaBindingsを選びました。



こんな感じにしました。

メールアドレス用のNSTextFieldvalueFile's Ownermailをbindし、Continuosly Updates Valueをチェック、

パスワード用のNSSecureTextFieldvalueFile's Ownerpasswordをbindし、Continuosly Updates Valueをチェック、

Create UserおよびLog inボタンのenabledFile's OwnercanLogInをbind。

以上4ヶ所のCocoaBindingsを設定します。

ボタンのアクションは後ほど設定します。

NSWindowのVisible At Launchはオフにします。

ソースコードです。基本部分。


LogInPanelController.swift

import Cocoa

import FirebaseAuth

class LogInPanelController: NSWindowController {

@objc dynamic private var mail: String = ""
@objc dynamic private var password: String = ""

@objc dynamic private var canLogIn: Bool {

// return isValidMail() && isValidPassword()
return mail != "" && password != ""
}

override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {

switch key {

case #keyPath(canLogIn):
return [#keyPath(mail), #keyPath(password)]

default:
return []
}
}

override var windowNibName: NSNib.Name {

return NSNib.Name("LogInPanelController")
}
}


ここまでは特に問題ないでしょう。メールアドレスとパスワードのバリデーションは手抜きです。


ユーザーの作成

まずはコードを見てください。


LogInPanelController.swift

extension LogInPanelController {

@IBAction private func createUser(_: Any) {

guard canLogIn else { fatalError("ここに来るとはなさけない。") }

Auth.auth().createUser(withEmail: mail, password: password) { result, error in

if let error = error as NSError?, let code = AuthErrorCode(rawValue: error.code) {

switch code {

case .invalidEmail:
print("Mail address is invalid.")

case .emailAlreadyInUse:
print("Mail address is already use.")

case .operationNotAllowed:
print("Operation not allowed")

case .weakPassword:
print("Password is weak.")

default:
print("なんかへん。", error)
}

return
}

guard result?.user != nil else {

print("Can not create user.")
return
}

print("Success create user.")
}
}
}


Authクラスのauth()関数からデフォルトのAuthのインスタンスを取得します。

通常はこのデフォルトのインスタンスのみ使用します。

Authのインスタンスを介して認証に関するさまざまな処理が実行可能です。

ユーザーの作成はその名もずばり、createUser(withEmail:,password:,completion:)メソッドを使用します。

completionはユーザーの作成が成功または失敗した後にメインスレッド上で呼び出されます。

型は (AuthDataResult?, Error?) -> Voidです。

Errorのcodeからenum AuthErrorCodeが作れますのでちょっとだけSwiftっぽいです。


認証

まずはコードをみてください。


LogInPanelController

extension LogInPanelController {

@IBAction private func logIn(_: Any) {

guard canLogIn else { fatalError("ここに来るとはなさけない。") }

Auth.auth().signIn(withEmail: mail, password: password) { result, error in

if let error = error as NSError?, let code = AuthErrorCode(rawValue: error.code) {

switch code {

case .invalidEmail:
print("Mail address is invalid.")

case .wrongPassword:
print("Mail or Password is wrong.")

case .userNotFound:
print("Not registered user.")

case .userDisabled:
print("User account is BAN.")

default:
if let email = result?.user.email {

print("log in", email)
}
}
}
}
}
}


認証はsignIn(withEmail:,password:,completion:)メソッドを使用します。

createUser(withEmail:,password:,completion:)とほぼ同じです。

問題ないでしょう。


アクションの設定

createUser:logIn:の両方のアクションが出来ましたので、xibで各ボタンにアクションを設定します。


サインアウト

サインアウトが出来るように最初に見たsignOut(_:)アクションを作成します。

これはAppDelegateで実装します。


AppDelegate.swift

extension AppDelegate {

@IBAction private func signOut(_: Any) {

try? Auth.auth().signOut()
}
}


Keychainに問題があるときにthrowされることがあるようです。

今回は捨ててます。


ウインドウの表示

ログインの状態によって表示するウインドウを変更します。

これもAppDelegateで実装します。

まずコードを見てください。


AppDelegate.swift

import Cocoa

import FirebaseCore
import FirebaseAuth

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

private let mainWindow = MainWindowController()

private let loginPanel = LogInPanelController()

func applicationWillFinishLaunching(_ notification: Notification) {

FirebaseApp.configure()
}

func applicationDidFinishLaunching(_ aNotification: Notification) {

Auth.auth().addStateDidChangeListener { auth, user in

if user == nil {

self.mainWindow.window?.orderOut(nil)
self.loginPanel.showWindow(nil)

} else {

self.loginPanel.window?.orderOut(nil)
self.mainWindow.showWindow(nil)

}
}
}
}


applicationDidFinishLaunching(_:)が該当部分です。

addStateDidChangeListener(_:)メソッドで関数を登録します。

この関数はユーザーのログイン状態が変化したときに呼び出されます。

関数の型は(Auth, User?) -> Voidです。

userには現在のユーザを表すUserのインスタンスが入ります。

ログインしていない場合はnilです。

これを利用して、表示するウインドウをトグルしています。

本来はaddStateDidChangeListener(_:)メソッドの返値を保存しておいて、不要になったらremoveStateDidChangeListener(_:)で解除する必要があるのですが、今回のアプリでは終了するまで監視し続けますので値は捨ててます。


おわり

これで認証編はおわりです。

本当はプロジェクトをGithubにでもあげればいいのですが、GoogleService-Info.plistの扱いが面倒くさいのであげていません。

GoogleService-Info.plistを入れずにGitHubにあげました。

https://github.com/masakih/FirebaseMacOS


次回

次回はRealtime Database編です。

macOSでFirebaseを使う ~ 準備編

macOSでFirebaseを使う ~ Realtime Database用準備編

macOSでFirebaseを使う ~ Realtime Database編

macOSでFirebaseを使う ~ おまけ