iOS 跨平台帳號密碼整合,加強登入體驗

ZhgChgLi
ZRealm Dev.
Published in
12 min readFeb 2, 2021

--

除 Sign in with Apple 也值得加入的功能

Photo by Dan Nelson

功能

在同時有網站又有 APP 的服務中最常遇到的問題就是使用者在網站登入註冊過,且有記憶密碼;但被引導安裝 APP 後,打開登入要從頭輸入帳號密碼非常不方便;此功能就是能將已存在在手機的帳號密碼自動帶入到與網站關聯的 APP 之中,加速使用者登入流程。

效果圖

不囉唆,先上完成效果圖;第一眼看到可能會以為是 iOS ≥ 11 Password AutoFill 功能;不過請您仔細看,鍵盤並沒有跳出來,而且我是點擊「選擇已存密碼」按鈕才跳出帳號密碼選擇視窗的。

既然提到了 Password AutoFill 那就先讓我賣個關子,先介紹 Password AutoFill 和如何設置吧!

Password AutoFill

支援度:iOS ≥ 11

到如今已經 iOS 14 了,這個功能已經非常常見沒什麼特別的;在 APP 中的帳號密碼登入頁,叫出鍵盤輸入時可以快速選擇網站版服務的帳號密碼,選擇後就能自動帶入,快速登入!

那麼 APP 與 Web 之間是如何相認的呢?

Associated Domains!我們在 APP 中指定 Associated Domains 並在網站上上傳 apple-app-site-association 檔案,兩邊就能相認。

1.在專案設定中的「Signing & Capabilities」-> 左上「+ Capabilities」->「Associated Domains」

新增 webcredentials:你的網站域名 (ex: webcredentials:google.com)。

2.進入蘋果開發者後台

在「Membership」Tab 地方記錄下「 Team ID

3.進入「Certificates, Identifiers & Profiles」->「Identifiers」-> 找到你的專案 -> 打開「Associated Domains」功能

APP 端設定完成!

4.Web網站端設定

建立一個名為「apple-app-site-association」的檔案(無副檔名),使用文字編輯器編輯,並輸入以下內容:

{
"webcredentials": {
"apps": [
"TeamID.BundleId"
]
}
}

TeamID.BundleId 換成你的專案設定 (ex: TeamID = ABCD, BundleID = li.zhgchg.demoapp => ABCD.li.zhgchg.demoapp)

將此檔案上傳到網站根目錄/.well-known 目錄下,假設你的webcredentials 網站域名是設 google.com 則此檔案就要是 google.com/apple-app-site-associationgoogle.com/.well-know/apple-app-site-association 有辦法存取到的。

補充:Subdomains

摘錄官方文件,如果是 subdomains 則都須列在 Associated Domains 之中。

Web 端設定完成!

補充:applinks

這邊有發現如果有設過 universal link applinks ,其實不用再多加 webcredentials 部分也能有效果;但我們還是照文件來吧,難保之後不會有其他問題。

回到程式

Code 部分,我們只需要將 TextField 設為 :

usernameTextField.textContentType = .username
passwordTextField.textContentType = .password

如果是新註冊,密碼確認欄位可使用:

repeatPasswordTextField.textContentType = .newPassword

這時候再重 Build & Run APP 後,在輸入帳號時鍵盤上方就會出現同個網站下已存密碼的選項了。

完成!

沒出現?

可能是沒打開自動填寫密碼功能(模擬器預設是關閉),請到「設定」->「密碼」->「自動填寫密碼」->打開「自動填寫密碼」。

抑或是該網站沒有已存在的密碼,一樣可在「設定」->「密碼」-> 右上角「+ 新增」-> 新增。

進入主題

前菜 Password AutoFill 介紹完之後,再來進入本篇主題;如何達到效果圖中的效果呢。

Shared Web Credentials

始於 iOS 8.0 只是之前很少看到 APP 使用,早在 Password AutoFill 出來之前其實就能使用此 API 整合網站帳號密碼讓使用者快速選擇。

Shared Web Credentials 除了能讀取帳號密碼,還能新增帳號密碼、對已存的帳號密碼進行修改、刪除。

設定

⚠️ 設定部分一樣要設好 Associated Domains,同前述 Password AutoFill 設定。

所以可以說是 Password AutoFill 功能的加強版!!

因為一樣要先設好 Password AutoFill 需要的環境才能使用此「進階」功能。

讀取

讀取使用 SecRequestSharedWebCredential 方法進行操作:

SecRequestSharedWebCredential(nil, nil) { (credentials, error) in
guard error == nil else {
DispatchQueue.main.async {
//alert error
}
return
}

guard CFArrayGetCount(credentials) > 0,
let dict = unsafeBitCast(CFArrayGetValueAtIndex(credentials, 0), to: CFDictionary.self) as? Dictionary<String, String>,
let account = dict[kSecAttrAccount as String],
let password = dict[kSecSharedPassword as String] else {
DispatchQueue.main.async {
//alert error
}
return
}

DispatchQueue.main.async {
//fill account,password to textfield
}
}

SecRequestSharedWebCredential(fqdn, account, completionHandler)

  • fqdn 如果有多個 webcredentials domain 可以指定某一個,或使用 null 不指定
  • account 指定要查某一個帳號,使用 null 不指定

效果圖。(你可能有發現跟開始的效果圖不一樣)

⚠️ 因為此讀取方法已在 iOS 14 被標示 Deprecated!
⚠️ 因為此讀取方法已在 iOS 14 被標示 Deprecated!
⚠️ 因為此讀取方法已在 iOS 14 被標示 Deprecated!

"Use ASAuthorizationController to make an ASAuthorizationPasswordRequest (AuthenticationServices framework)"

此方法僅適用 iOS 8 ~ iOS 14,iOS 13 之後可改用同 Sign in with Apple 的 API — 「AuthenticationServices

AuthenticationServices 讀取方式

支援度 iOS ≥ 13

import AuthenticationServices

class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//...
let request: ASAuthorizationPasswordRequest = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.performRequests()
//...
}
}

extension ViewController: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {

if let credential = authorization.credential as? ASPasswordCredential {
// fill credential.user, credential.password to textfield
}
// else if as? ASAuthorizationAppleIDCredential... sign in with apple
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// alert error
}
}

效果圖,可以看到新的做法在流程上、顯示上都能跟 Sign in with Apple 整合得更好。

⚠️ 此登入無法取代 Sign in with Apple(兩個是不同東西)。

寫入帳號密碼到「密碼」

被 Deprecated 的只有讀取的部分,新增、刪除、編輯的部分都還是照舊能用。

新增、刪除、編輯的部分使用 SecAddSharedWebCredential 進行操作。

SecAddSharedWebCredential(domain as CFString, account as CFString, password as CFString?) { (error) in
DispatchQueue.main.async {
guard error == nil else {
// alert error
return
}
// alert success
}
}

SecAddSharedWebCredential(fqdn, account, password, completionHandler)

  • fqdn 可隨意指定要存入的 domain 不一定要在 webcredentials
  • account 指定要新增、修改、刪除的帳號
  • 如果要刪除資料則將 password 帶入 nil
  • 處理邏輯:
    - account 存在&有帶入 password = 修改 password
    - account 存在&password 帶入 nil = 從 domain 刪除 account, password
    - account 不存在&有帶入 password = 新增 account, password 到 domain

⚠️另外也不是能讓你在背景偷修改的,每次修改都會跳出提示框提示使用者,使用者按「更新密碼」才會真的修改資料。

密碼產生器

最後一個小功能,密碼產生器。

使用 SecCreateSharedWebCredentialPassword() 進行操作。

let password = SecCreateSharedWebCredentialPassword() as String? ?? ""

產生器產生出來的 Password 由英文大小寫及數字並使用「-」組成 (ex: Jpn-4t2-gaF-dYk)。

完整測試專案下載

美中不足

如果有使用第三方密碼管理工具(EX: onepass、lastpass)的朋友可能會發現,如果是鍵盤的 Password AutoFill 能支援顯示&輸入,但是在 AuthenticationServices 或 SecRequestSharedWebCredential 當中都沒有顯示出來;不確定有沒有辦法達成這個需求。

結束

感謝大家閱讀,也感謝 saiday 、街聲讓我知道有這個功能 XD。

還有 XCode ≥ 12.5 模擬器新增錄影,並支援儲存成 GIF 功能太好用啦!

在模擬器上按「Command」+「R」開始錄影,按一下紅點停止錄影;在右下角滑出的預覽圖上按「右鍵」->「Save as Animated GIF」即可存成 GIF 然後直接貼到文章內!

有任何問題及指教歡迎與我聯絡

--

--

ZhgChgLi
ZRealm Dev.

探索世界、求知若渴、教學相長;更愛電影、美劇、西音、運動、生活. www.zhgchg.li