1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【iOS】Auth0 SDKでUniversal Loginを実装する

1
Posted at

はじめに

Auth0 を使った iOS アプリの認証機能の実装を経験しました。
せっかくなので、復習を兼ねて Auth0 iOS SDK を使って Universal Login によるログインを実装する方法 をまとめます。

本記事では、UIKit + Swift Package Manager(SPM) で、

  • ログイン
  • ログアウト
  • 認証情報の保存/復元

までを一通り実装します。

Auth0とは

Auth0 は、Okta 社が提供している認証・認可プラットフォームです。
Web アプリケーションだけでなく、モバイルアプリケーション(iOS / Android)にも対応しています。
Auth0 には Universal Login という仕組みがあり、これを用いることで

  • ログイン UI
  • 認証フロー(ID/パスワード、パスキー認証、SNSログインなど)

を Auth0 側に任せることができます。

今回作るもの

今回は以下のアプリケーションを作成します。

  • ログインボタンタップ時、Universal Login を表示する Auth0のログイン処理 を実行
    • ログイン処理成功時
      • 認証情報を保存
      • ログインユーザーのメールアドレスを画面に表示
  • ログアウトボタンタップ時、ブラウザのセッション情報を破棄する Auth0のログアウト処理 を実行
    • ログアウト処理成功時
      • 認証情報を破棄
      • 「未ログイン」を表示
  • アプリ起動時、保存されている認証情報を取得
    • 有効な認証情報がある場合、ユーザーのメールアドレスを画面に表示
    • それ以外の場合、「未ログイン」を表示

作成後のアプリケーション
output.gif

環境

Auth0 側の事前準備

0. Auth0アカウントの作成・テナント準備

Auth0 を利用するには、アカウント登録とテナントの作成が必要です。
Auth0 には無料プランがあり、今回の内容は無料プラン内で実施できます。

1. Auth0 アプリケーション作成

  1. Auth0 ダッシュボードの アプリケーション ページを開き、「+アプリケーションを作成」をタップ

    image.png

  2. 任意のアプリケーション名を入力し、アプリケーションの種類を ネイティブ で選択し、「作成」をタップ
    以下はアプリケーション名を My Mobile App とした場合です。
    image.png

2. ログイン用ユーザーの作成

動作確認用に、ログインで使用するユーザーを作成します。

  1. Auth0 ダッシュボードの ユーザー ページを開き、ページ右上の 「+ユーザー作成」 > 「UIで作成」 をタップ

    image.png

  2. 必要な項目を入力し、「作成」をタップ

    image.png

iOS プロジェクトの準備・SDKインストール

0. iOS プロジェクト作成

Xcode で新規に iOS プロジェクトを作成します。

  • Interface:Storyboard
  • Language:Swift

※ 既存プロジェクトに組み込む場合、この手順は不要です。

1. Auth0 iOS SDK を SPM でインストール

今回は Swift Package Manager(SPM) を使用して、 Auth0 SDK をインストールします。

  1. Xcode メニューから

    File > Add Package Dependencies… を選択
    image.png

  2. 右上の検索欄に以下の URL を指定し検索し、「auth0.swift」を選択して、「Add Package」をタップ

    https://github.com/auth0/Auth0.swift
    

    image.png

  3. Add to Target で対象を選択して「Add Package」をタップ
    image.png

これで Auth0 SDK の導入は完了です。

以下のパッケージが追加されていると思います。

  • Auth0
  • JWTDecode
  • SimpleKeychain

2. Auth0.plist の作成

Auth0 アプリケーションと通信するために、以下の情報が必要です。

  • Client ID
  • Domain

これらの情報は Auth0.plist を作成し、そこに記載する必要があります。

  1. Xcode メニューから

    File > New > File from Template… を選択
    image.png

  2. 「Property List」を選択し、「Next」をタップ

    ファイル名をAuth0.plist として「Create」をタップする
    image.png

  3. Auth0.plist に以下の内容を記載する

    {}で囲われている部分はご自身のAuth0アプリケーションの内容に置き換えてください。

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>ClientId</key>
        <string>{Auth0 アプリケーションの Client ID(キャプチャの赤枠)}</string>
        <key>Domain</key>
        <string>{Auth0 アプリケーションの Domain(キャプチャの青枠)}</string>
    </dict>
    </plist>
    

    image.png

    例:

    • Auth0 アプリケーションの Client ID : aaa
    • Auth0 アプリケーションの Domain:bbb
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>ClientId</key>
        <string>aaa</string>
        <key>Domain</key>
        <string>bbb</string>
    </dict>
    </plist>
    
    

Auth0アプリケーションに Callback URL / Logout URL の設定

Universal Login では、ログイン・ログアウト完了後に ブラウザからアプリへ戻るための URL が必要です。

  • ログイン完了後 → Callback URL
  • ログアウト完了後 → ログアウト URL

設定する値は以下で、どちらも同じ値になります。

{}で囲われている部分はご自身の環境の内容に置き換えてください。

{iOSアプリのBundleIdentifier}://{Auth0 アプリケーションの Domain}/ios/{iOSアプリのBundleIdentifier}/callback

例:

  • iOSアプリのBundleIdentifier:com.sample.auth0
  • Auth0 アプリケーションの Domain:bbb
com.sample.auth0://bbb/ios/com.sample.auth0/callback

Auth0 アプリケーションの設定画面を開き、

  • 許可する Callback URL
  • 許可するログアウト URL

に上記の URL を設定し、「保存」をタップします。

image.png

本記事では最小構成とするため、iOS の カスタムURLスキーム を用いて Callback URL / Logout URL を設定しています。
Universal Links を利用することも可能ですが、その場合はAssociated Domainsの設定や Auth0アプリケーションへの追加の設定などが必要になるため、本記事では扱っていません。
https://auth0.com/docs/ja-jp/quickstart/native/ios-swift/interactive#callback-url%E3%81%A8%E3%83%AD%E3%82%B0%E3%82%A2%E3%82%A6%E3%83%88url%E3%82%92%E6%A7%8B%E6%88%90%E3%81%99%E3%82%8B

ログイン処理の実装

前準備

Storyboard 上に以下を配置します。

  • ログインボタン UIButton(ログインボタン)
  • ログイン状態を表示する UILabel

image.png

ViewController に IBOutlet / IBAction を接続します。

  • ログインボタンをタップした時の処理(didTapLoginButton)
  • ログイン状態を表示する UILabel(textLabel)
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var textLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        textLabel.text = "未ログイン"
    }

    @IBAction func didTapLoginButton(_ sender: UIButton) {
    }
}

ログイン処理(Auth0.webAuth().start)

ログインボタンタップ時の処理(didTapLoginButton)に、Auth0のログイン処理を記載します。

この処理を呼び出すと、Auth0の Universal Login が表示されます。
ログイン成功時、取得した ID Token からメールアドレスを取り出して画面に表示し、
ログイン失敗時、エラーの内容を表示するようにしています。

import UIKit
import Auth0
import JWTDecode

class ViewController: UIViewController {
    
...

    @IBAction func didTapLoginButton(_ sender: UIButton) {
        // Auth0 ログイン処理
        Auth0
            .webAuth()
            .start {[weak self] result in
                guard let self else { return }
                switch result {
                case .success(let credentials):
                    // ログイン成功時
                    guard let idToken = try? decode(jwt: credentials.idToken),
                          let userEmail = idToken["email"].string else { return }
                    print("idToken: \(idToken)")
                    self.textLabel.text = "ログインユーザー: \(userEmail)"
                case .failure(let error):
                    // ログイン失敗時
                    self.textLabel.text = "ログイン失敗: \(error)"
                }
            }
    }
}

認証成功時、ユーザーが認証されたことを示す ID Token を取得できます。
ID Token にはユーザー情報が内包されています。
JWT形式のためデコードすることで、ユーザー名やメールアドレスなどのユーザー情報を参照できます。

guard let idToken = try? decode(jwt: credentials.idToken),
      let userEmail = idToken["email"].string else { return }

以下のサイトに ID Token を貼り付けると中身を確認できます。

Auth0ログイン処理では、.scope().audience() などのオプション指定が可能です。
本記事では最小構成のため指定していませんが、用途に応じて設定してください。
例えば、リフレッシュトークンを受け取る場合は、.scope("openid offline_access") のような指定が必要です。

ログアウト処理の実装

前準備

Storyboard 上に以下を配置します。

  • ログアウト用 UIButton(ログアウトボタン)

image.png

ViewController に以下の処理を追加

  • ログアウトボタンをタップした時の処理(didTapLogoutButton)
import UIKit
import Auth0
import JWTDecode

class ViewController: UIViewController {
 
...
 
    @IBAction func didTapLogoutButton(_ sender: UIButton) {
    }
    
}

ログアウト処理(Auth0.webAuth().clearSession)

ログアウトボタンタップ時の処理(didTapLogoutButton)に、Auth0のログアウト処理を記載します。

@IBAction func didTapLogoutButton(_ sender: UIButton) {
    // Auth0 ログアウト処理
    Auth0
        .webAuth()
        .clearSession {[weak self] result in
            guard let self else { return }
            switch result {
            case .success:
                // ログアウト成功
                self.textLabel.text = "未ログイン"
            case .failure(let error):
                // ログアウト失敗
                self.textLabel.text = "ログアウト失敗: \(error)"
            }
        }
}

Universal Login では、ログイン成功時ブラウザ側に認証セッションが残ります。
ログイン実行時、ブラウザに認証セッションがある場合、ログインID・パスワードを入力する認証画面がスキップされます。
これにより1つのアプリで認証を行うことで、別アプリへの認証をスキップできるSSOが可能になります。
別ユーザーでのログインを行うためには、ブラウザのセッションを明示的に破棄する Auth0のログアウト処理が必要になります。

Auth0ログイン処理にて.useEphemeralSession()を指定することで、ブラウザに認証情報を保存する機能をオフにできます。
この場合は認証情報が保存されないのでAuth0のログアウト処理を実施する必要はなく、アプリ内の認証情報を破棄するだけで良くなります。
ただしSSOの機能は使用できなくなります。
https://github.com/auth0/Auth0.swift/blob/master/FAQ.md#if-you-dont-need-sso

認証情報の保存・復元・破棄

ログイン成功時に取得した認証情報の保存は、
Auth0 SDK が提供する CredentialsManager を使って行えます。

CredentialsManager のインスタンスを保持するプロパティを作成

import UIKit
import Auth0
import JWTDecode

class ViewController: UIViewController {
    
    @IBOutlet weak var textLabel: UILabel!
    
    let credentialsManager: CredentialsManager = CredentialsManager(authentication: Auth0.authentication())

...
}

ログイン成功時、認証情報を保存する(.store()

ログイン成功時に、以下の認証情報を保存する処理を追加します。

@IBAction func didTapLoginButton(_ sender: UIButton) {
    // Auth0 ログイン処理
    Auth0
        .webAuth()
        .start {[weak self] result in
            guard let self else { return }
            switch result {
            case .success(let credentials):
                // ログイン成功時
                guard let idToken = try? decode(jwt: credentials.idToken),
                      let userEmail = idToken["email"].string else { return }
                print("idToken: \(idToken)")
                self.textLabel.text = "ログインユーザー: \(userEmail)"
                
                // 認証情報を保存
                if self.credentialsManager.store(credentials: credentials) {
                    print("認証情報保存成功")
                }
            case .failure(let error):
                // ログイン失敗時
                self.textLabel.text = "ログイン失敗: \(error)"
            }
        }
}

ログアウト成功時、認証情報を破棄する(CredentialsManager.clear()

ログアウト成功時に、認証情報を破棄する処理を追加します。

@IBAction func didTapLogoutButton(_ sender: UIButton) {
    // Auth0 ログアウト処理
    Auth0
        .webAuth()
        .clearSession {[weak self] result in
            guard let self else { return }
            switch result {
            case .success:
                self.textLabel.text = "未ログイン"
                // 認証情報を破棄
                if self.credentialsManager.clear() {
                    print("認証情報破棄成功")
                }
            case .failure(let error):
                self.textLabel.text = "ログアウト失敗: \(error)"
            }
        }
}

アプリ起動時、保存した認証情報を取得する

アプリ起動時、有効な認証情報が存在するかをチェックし、ユーザーのメールアドレスを取得する処理を追加する。

override func viewDidLoad() {
    super.viewDidLoad()
    // 認証情報をチェックし、ログインユーザーのメールアドレスを取得
    if credentialsManager.hasValid(),
       let userEmail = credentialsManager.user?.email {
        self.textLabel.text = "ログインユーザー: \(userEmail)"
        return
    }
    
    textLabel.text = "未ログイン"
}

これでログイン時に受け取った認証情報を保存することができ、アプリ起動時に認証情報を利用できます。

今回はユーザー情報を取り出しましたが、以下のようにすれば認証時に受け取ったアクセストークンを取得することもできます。

let cre = CredentialsManager(authentication: Auth0.authentication())
cre.credentials { result in
    switch result {
    case .success(let credentials):
        let accessToken = credentials.accessToken
        print(accessToken)
    case .failure(let error):
        print(error)
    }
}

まとめ

今回は Auth0 iOS SDK を用いた Universal Login の実装方法をご紹介しました。

Auth0 の Universal Login を使うことで、認証 UI や認証処理を自前で実装する必要がなく、
iOS 側では SDK を利用してログイン/ログアウト処理を呼び出すだけで良いです。

また Auth0 iOS SDK を用いることで、ログイン/ログアウトだけではなく、JWTのデコードや認証情報の保存を行うことができます。

最終コード

import UIKit
import Auth0
import JWTDecode

class ViewController: UIViewController {
    
    @IBOutlet weak var textLabel: UILabel!
    
    let credentialsManager: CredentialsManager = CredentialsManager(authentication: Auth0.authentication())
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 認証情報をチェックし、ログインユーザーのメールアドレスを取得
        if credentialsManager.hasValid(),
           let userEmail = credentialsManager.user?.email {
            self.textLabel.text = "ログインユーザー: \(userEmail)"
            return
        }
        
        textLabel.text = "未ログイン"
    }

        
    @IBAction func didTapLoginButton(_ sender: UIButton) {
        // Auth0 ログイン処理
        Auth0
            .webAuth()
            .start {[weak self] result in
                guard let self else { return }
                switch result {
                case .success(let credentials):
                    // ログイン成功時
                    guard let idToken = try? decode(jwt: credentials.idToken),
                          let userEmail = idToken["email"].string else { return }
                    print("idToken: \(idToken)")
                    self.textLabel.text = "ログインユーザー: \(userEmail)"
                    
                    // 認証情報を保存
                    if self.credentialsManager.store(credentials: credentials) {
                        print("認証情報保存成功")
                    }
                case .failure(let error):
                    // ログイン失敗時
                    self.textLabel.text = "ログイン失敗: \(error)"
                }
            }
    }
        
    @IBAction func didTapLogoutButton(_ sender: UIButton) {
        // Auth0 ログアウト処理
        Auth0
            .webAuth()
            .clearSession {[weak self] result in
                guard let self else { return }
                switch result {
                case .success:
                    self.textLabel.text = "未ログイン"
                    // 認証情報を破棄
                    if self.credentialsManager.clear() {
                        print("認証情報破棄成功")
                    }
                case .failure(let error):
                    self.textLabel.text = "ログアウト失敗: \(error)"
                }
            }
    }
    
}

参考

https://auth0.com/docs/ja-jp/libraries/auth0-swift
https://github.com/auth0/Auth0.swift/tree/master
https://dev.classmethod.jp/articles/auth0-access-token-id-token-difference/
https://auth0.com/blog/id-token-access-token-what-is-the-difference/

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?