Yahoo! ID連携

iOSアプリ

  • Hybridフロー
  • Implicitフロー

Hybridフロー

iOSのHybridフローのサンプルコードの説明をします。

ライブラリーをプロジェクトに追加する

Yahoo! ID連携 iOS SDKを任意の場所にダウンロードして解凍してください。
解凍後、YConnectSDK.xcodeprojをアプリケーションのプロジェクトに追加してください。詳細な手順を下記に示します。

  1. XCodeでアプリケーションのプロジェクトを開いてください。

  2. XCodeのメニューから"File" > "Add Files To <プロジェクト名>"を選択してください。

  3. 解凍したYahoo! ID連携 iOS SDK内のYConnect.xcodeprojを選択し、"Add"してください。

プロジェクトの設定

はじめにYahoo! ID連携 iOS SDKを利用するためにプロジェクトの設定を行います。

まずはビルドターゲットの"Build Phase"を開き、"Link Binary With Libraries"の"+"ボタンを押してください。

ダイアログが表示されるので、WorkspaceからlibYConnect.aを選択し、"Add"してください。

次にYahoo! ID連携 iOS SDK内のヘッダファイルをimportするために設定を行います。

ビルドターゲットの"Build Settings"を開き、設定項目中の"Header Search Paths"にYahoo! ID連携 iOS SDKのパスを追加します。

最後にコールバックURLを設定します。

ビルドターゲットの"Info"を開き、"URL Types"を展開してください。

"+"ボタンを押すと入力フォームが表示されるので、"identifier"に一意な値を、

"URL schemes"にはこのschemeはClient ID登録時に発行されるカスタムURIスキーム「yj-xxxxx」を指定してください。

"Identifier"に指定する値は"Bundle identifier"と同じ値が推奨されています。

また、カスタムURIスキームはアプリケーションの管理から変更可能です。

Swiftアプリケーションで使用する場合の設定

Yahoo! ID連携 iOS SDKはObjective-Cコードのため、Swiftアプリケーションで使用する場合にはプロジェクトにBridging Headerファイルを追加する必要があります。

Bridging Headerファイルがプロジェクトに追加されていない場合は、以下の手順で追加を行なってください。

  1. XCodeのメニューから"File" > "New" > "File"を選択してください。

  2. "Header File"を選択してください。

  3. ファイル名を"Bridging-Header.h"とし、"Create"してください。


続いてBridging Headerファイルを使用するための設定を行います。

ビルドターゲットの"Build Settings"を開き、"Swift Compiler"の"Objective-C Bridging Header"にBridging Headerファイルのパスを追加します。

Bridging HeaderファイルにはYahoo! ID連携 iOS SDKのヘッダファイルをimportするコードを追加してください。

Bridging-Header.h
#ifndef Bridging_Header_h
#define Bridging_Header_h

#import "YConnect.h"

#endif /* Bridging_Header.h */

設定の初期化をする

  1. Info.plistにClient ID, コールバックURL, レスポンスタイプ, スコープ(複数ある場合はスペース区切り)を記載してください

  2. plistに記載するキーと値の内訳は下記のとおりです。

  3. Key Type 説明 入力例
    YConnectClientIdStringClient IDを指定します。
    dj00zaiZpPXNYeG5tRXJVVlLzPuW
    NMvbnN1bWVyc2VjcmV0Jng9ZWU-
    YConnectRedirectUriStringClient ID発行時に登録したコールバックURIを指定します。
    yj-xxxxx:/
    
    YConnectDisplayStringログイン画面、同意画面のテンプレートの種類を指定する場合に利用します。
    touch
    
    ※任意のパラメーターです。
    YConnectResponseTypeString「code id_token」「code token」「code id_token token」のいずれかを指定してください。
    code%20id_token
    
    ※URLエンコードが必要です。
    YConnectScopeStringUserInfo APIから取得できる属性情報を指定できます。
    openid address
    
    YConnectBailBooleanYESにすると同意画面で「同意しない」ボタンをクリックした際にcodeパラメーターを付加せずにredirect_uriへリダイレクトさせます。指定しない場合はYahoo!トップへ遷移します。
    YES
    
    ※任意のパラメーターです。
    YConnectMaxAgeNumber最大認証経過時間を指定します。指定された秒数よりも認証日時が経過していた場合は再認証が必要です。
    3600
    
    ※任意のパラメーターです。

Authorizationエンドポイントにリクエストして同意画面を表示する

YConnectManagerクラスにはHybridフローで必要なメソッドがすべて実装されています。
YConnectManagerクラスを使って、アプリ内ブラウザタブであるASWebAuthenticationSession、SFAuthenticationSessionもしくはSFSafariViewControllerに同意画面を表示させます。

なお、サンプルコードはStoryboardを使用しています。

YConnectManagerクラス

メソッド説明
+ (instancetype)sharedInstanceシングルトンパターンのインスタンスを取得する。
- (NSURL *)generateAuthorizationUriWithState:(NSString *)state
  prompt:(NSString *)prompt
  nonce:(NSString *)nonce
同意画面のURLを取得する。
- (void)requestAuthorizationWithState:(NSString *)state
  prompt:(NSString *)prompt
  nonce:(NSString *)nonce
  presentingViewController:(UIViewController *)presentingViewController
  API_AVAILABLE(ios(9.0))
ASWebAuthenticationSession、SFAuthenticationSession、もしくはSFSafariViewControllerで同意画面を表示する。

※iOS9〜iOS10をサポート対象に含める場合は必ずこちらを利用してください。
- (void)requestAuthorizationWithState:(NSString *)state
  prompt:(NSString *)prompt
  nonce:(NSString *)nonce
  handler:(YConnectAuthorizationEndpointResponseHandler)handler
  API_AVAILABLE(ios(11.0))
ASWebAuthenticationSession、SFAuthenticationSessionで同意画面を表示する。
ログイン成功時は自動でコールバックURLのフラグメントに付加されたパラメーターを抽出する。

※iOS9〜iOS10をサポート対象に含めず、iOS11以上をサポート対象とする場合はこちらの利用を推奨します。

OIDCPromptクラス

定数フィールド値説明
YConnectConfigPromptConsentユーザーの再認可のための定数です。
YConnectConfigPromptLoginユーザーの再認証のための定数です。
YConnectConfigPromptNone同意画面非表示のための定数です。(ログイン状態でなかった場合、過去に同意済みでなかった場合はエラーが返却されます)
YConnectConfigPromptSelectAccountID切り替え画面を強制表示するための定数です。
MainViewController.swift
@IBAction func login(_ sender: UIButton) {
    getStateAndNonce(callback: { (state, nonce, error) in
        // エラーハンドリング
        if let error = error {
            ...
            return
        }
        let yconnect = YConnectManager.sharedInstance()
        if #available(iOS 11.0, *) {
            // iOS11以上をサポートする場合はハンドラありのメソッドを使用します
            yconnect?.requestAuthorization(withState: state, prompt: nil, nonce: nonce, handler: { (error) in
                // エラーハンドリング
                if let error = error {
                    ...
                    return
                }
                self.performSegue(withIdentifier: "ToResultPage", sender: nil)
            })
        } else if #available(iOS 9.0, *) {
            // iOS9〜iOS10をサポートする場合はハンドラなしのメソッドを使用します
            // レスポンスパラメータを取得するにはAppDelegate内にparseAuthorizationResponseの処理を記述する必要があります
            yconnect?.requestAuthorization(withState: state, prompt: nil, nonce: nonce, presenting: self)
        }
    })
}

private func getStateAndNonce(callback: (String?, String?, Error?) -> Void) -> Void {
    // state, nonceの取得(認証サーバー側にstateとnonceを発行するための任意のURLを用意してください)
    let url = NSURL(string: "https://example.com/issue.php")
    var state: String?
    var nonce: String?
    do {
        let jsonData = try Data(contentsOf: url! as URL)
        let json = try JSONSerialization.jsonObject(with: jsonData) as! Dictionary<String, Any>
        state = json["state"] as? String
        nonce = json["nonce"] as? String

        // エラーハンドリング
        guard let state = state, let nonce = nonce else {
            ...
            return
        }
        callback(state, nonce, nil)
    } catch {
        // エラーハンドリング
        ...
        return
    }
}

認証サーバー側で発行するstateとnonceは独自の仕様でランダムな文字列を生成してデータベースなどに保存してください。
stateはAuthorizationエンドポイントからのコールバックURL受取時と認証サーバーへの認可コード連携時に、nonceはIDトークンを復号時の検証に必要です。

各パラメーター値の詳細についてはAuthorizationエンドポイントを参照してください。

サンプルでは各パラメーターを乗せたURLにリクエストしています。

コールバックURLを受け取り、認可コード、Access Token、ID Tokenを抽出する

iOS9〜iOS10をサポート対象に含める場合は、AppDelegateでコールバックURLを受け取るための実装が必要です。

同意後に返されるコールバックURLをAppDelegateで受け取り、"parseAuthorizationResponse"メソッドを用いてAccess Tokenなどを抽出します。

YConnectManagerクラス

メソッド説明
- (void)parseAuthorizationResponse:(NSURL *)uri
  handler:(YConnectAuthorizationEndpointResponseHandler)handler
コールバックURLのフラグメントに付加
されたパラメーターを抽出する。
- (void)fetchAccessToken:(NSString *)code
  handler:(YConnectTokenEndpointResponseHandler)handler
認可コードを用いてAccess Tokenを
取得する。
- (NSString *)accessTokenStringAccess Tokenを取得する。
- (NSInteger)accessTokenExpirationAccess Tokenの有効期限を取得する。
- (NSString *)hybridIdTokenStringID Tokenを取得する。
AppDelegate.swift
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    //コールバックURLに指定したURLだった場合、最初のViewControllerから結果を表示するResultViewControllerに遷移する
    if url.scheme == "yj-xxxx" {
        let yconnect = YConnectManager.sharedInstance()
        yconnect?.parseAuthorizationResponse(url, handler: { (error) in
            // エラーハンドリング
            if let error = error {
                ...
                return false
            }
            yconnect?.fetchAccessToken(yconnect?.authorizationCode, handler: { (retAccessToken, error) in
                // エラーハンドリング
                if let error = error {
                    ...
                    return false
                }
                // Access Token、ID Tokenを取得
                let accessToken = yconnect?.accessTokenString()
                let idToken = yconnect?.hybridIdTokenString()
            })
            let viewController = self.window?.rootViewController?.children[0]
            viewController?.dismiss(animated: true, completion: nil)
            viewController?.performSegue(withIdentifier: "ToResultPage", sender: viewController)
        })
        return true
    }
    return false
}

iOS9〜iOS10をサポート対象に含めず、iOS11以上のみをサポート対象とする場合はAppDelegateに追加の実装は必要ありません。

ハンドラありの"requestAuthorization"メソッド内で"parseAuthorizationResponse"メソッドが実行されるため、正常終了時は任意のタイミングでAccess Tokenなどの取得が可能です。

MainViewController.swift
if #available(iOS 11.0, *) {
    // iOS11以上をサポートする場合はハンドラありのメソッドを使用します
    yconnect?.requestAuthorization(withState: state, prompt: nil, nonce: nonce, handler: { (error) in
        // エラーハンドリング
        if let error = error {
            ...
            return
        }
        // Access Token、ID Tokenを取得
        let accessToken = yconnect?.accessTokenString()
        let idToken = yconnect?.hybridIdTokenString()
    })
}

取得した認可コードを認証サーバに連携する

認証サーバー側に認可コードとstateを渡し、認証サーバはTokenエンドポイントにリクエストしてAccess Token/Refresh Token/ID Tokenを発行します。

LoggedInResultViewController.swift
// コールバックURLから各パラメータを抽出
let yconnect = YConnectManager.sharedInstance()
// 認証サーバー側にcodeとstateを渡すURLを作成してください
guard let authorizationCode = yconnect?.authorizationCode, let state = yconnect?.state else {
    // エラーハンドリング
    ...
    return
}
let url = NSURL(string: "http://example.com/auth.php?code=\(authorizationCode)&state=\(state)")
/*
 * WebViewを起動して認証サーバに送信してください
 */

取得したID Tokenを検証した上でユーザー識別子を取得する

オプションでレスポンスタイプにID Tokenを指定した場合、ID Tokenの暗号化された文字列が返されます。
この文字列をクライアント側で検証し、ユーザー識別子やID Tokenの発行元などの情報を取得します。

LoggedInResultViewController.swift
{
    ...
    // コールバックURLから各パラメータを抽出
    let yconnect = YConnectManager.sharedInstance()

   /** IDトークンの検証が必要です
     *
     * 検証手順については下記リンク先の「ID Tokenの検証手順」をご参照ください
     * https://developer.yahoo.co.jp/yconnect/v2/id_token.html
     *
     * 検証に必要な公開鍵は以下から取得が可能です
    **/
    self.getDataFrom(url: YConnectConfigV2PublicKeysEndpoint, callback: { (publicKey, error) in
        // エラーハンドリング
        if let error = error {
            ...
            return
        }
        // エラーハンドリング
        guard let publicKey = publicKey else {
            ...
            return
        }
        let data = publicKey.data(using: String.Encoding.utf8)
        do {
            let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, Any>
        } catch {
            // エラーハンドリング
            ...
            return
        }
        // 検証済みのIDトークンを利用する
        let idTokenObject: YConnectIdTokenObject = (yconnect?.idTokenObject!)!
    })
}

private func getDataFrom(url: String, callback: @escaping (String?, Error?) -> Void)
{
    let request = NSURLRequest(url: URL(string: url)!)
    let configuration = URLSessionConfiguration.default
    let session = URLSession(configuration: configuration)
    let task = session.dataTask(with: request as URLRequest, completionHandler: {  (data, response, error) in
        if let httpResponse = response as? HTTPURLResponse {
            // エラーハンドリング
            if httpResponse.statusCode != 200 {
                callback(nil, error)
                return
            }
            // エラーハンドリング
            guard let data = data else {
                callback(nil, error)
                return
            }
            let responseData: String? = String(data: data, encoding: String.Encoding.utf8)
            callback(responseData, nil)
        }
    })
    task.resume()
}

各パラメーターはID Tokenオブジェクトのプロパティーに保持されているので、必要に応じて取り出して利用してください。

UserInfo APIにリクエストしてユーザー識別子を取得する

Access Tokenを用いてユーザー識別子などを取得します。

YConnectManagerクラス

メソッド説明
- (void)fetchUserInfo:(NSString *)accessTokenString
  handler:(YConnectUserInfoEndpointResponseHandler)handler
UserInfo APIにリクエストする。
LoggedInResultViewController.swift
// UserInfo情報を取得
private func fetchUserInfo()
{
    let yconnect = YConnectManager.sharedInstance()
    let accessToken = yconnect?.accessTokenString()

    // Access Tokenが空
    guard (accessToken != nil) else {
        ...
        return
    }

    yconnect?.fetchUserInfo(accessToken, handler: { (userInfoObject, error) in
        // エラーハンドリング
        if let error = error {
            ...
            return
        }
        // UserInfo情報からユーザー識別子を取得
        let userid = yconnect?.userInfoObject.sub
    })
}

Access Tokenを更新する

Refresh Tokenをつかって有効期限切れのAccess Tokenを更新します。

YConnectManagerクラス

メソッド説明
- (void)refreshAccessToken:(NSString *)refreshToken
  handler:(YConnectTokenEndpointResponseHandler)handler
Access Tokenを更新する
LoggedInResultViewController.swift
yconnect?.refreshAccessToken(yconnect?.refreshTokenString(), handler: { (accessToken, error) in
    if /* エラーレスポンスが"Invalid_Token"であるかチェック */ {
        // 再度認証を行う必要があります
    }
    //  更新に成功したAccess Tokenを利用できます
    ...
})

Access Tokenなどの保存について

Access Token、ID Token、UserInfo情報の保存は、悪意のある外部アプリケーションから盗みとられないように注意が必要です。 取得したトークンやユーザー情報を保存する場合には、適宜暗号化や難読化し外部から読み取られないようにしてください。

ログインボタンの指定と追加

Yahoo! ID連携 iOS SDK内の"YConnectAssets.xcassets"に含まれているログインボタンは、デフォルト画像としてアプリケーション内で使用できます。

"YConnectAssets.xcassets"には以下の4種類のログインボタンがデフォルト画像として用意されています。

さらにログインボタンをアレンジする場合は、Yahoo! JAPAN IDログインボタン デザインガイドラインに沿って利用してください。

画像名YConnectLoginButtonImageクラスのメソッド名表示されるログインボタン
yconnect_button_square+ (UIImage *)squareImage
yconnect_button_square_inverse+ (UIImage *)squareInverseImage
yconnect_button_circle+ (UIImage *)circleImage
yconnect_button_circle_inverse+ (UIImage *)circleInverseImage

デフォルトのログインボタンを使用するには、"YConnectAssets.xcassets"をプロジェクトに追加する必要があります。

まず、ビルドターゲットの"Build Phases"を開き、"Copy Bundle Resources"の"+"ボタンを押してください。

ダイアログが表示されるので、"Add Other..."から"YConnectAssets.xcassets"を"Open"してください。

以下ように"Copy Bundle Resources"に"YConnectAssets.acassets"が追加されていることを確認できたら完了です。

Storyboard上での指定方法

Storyboard上でログインボタンとして使用したい"Button"を選択し、"Attributes Inspector"の"Image"から任意の画像名を指定してください。

コード上での指定方法

ログインボタンとして使用したい"UIButton"インスタンスに対し、
以下のように"YConnectLoginButtonImage"クラスのメソッドを使用して任意の画像を指定してください。

MainViewController.swift
@IBOutlet weak var loginButton: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()

    // 表示させたいログインボタンを指定してください
    loginButton.setImage(YConnectLoginButtonImage.squareImage(), for: UIControl.State.normal)
}