iOS および macOS 用の MSAL にアプリケーションを移行する

Azure Active Directory認証ライブラリ (ADAL Objective-C) は、v1.0 エンドポイント経由でMicrosoft Entra アカウントを操作するために作成されました。

iOS および macOS 向け Microsoft Authentication Library(MSAL)は、Microsoft ID プラットフォーム(旧称 Azure AD v2.0 エンドポイント)を介して、Microsoft Entra アカウント、個人用 Microsoft アカウント、Azure AD B2C アカウントなど、Microsoft のあらゆる ID で動作するように設計されています。

Microsoft ID プラットフォームには、Azure AD v1.0 との主な違いがいくつかあります。 この記事では、これらの違いについて説明し、アプリを ADAL から MSAL に移行するためのガイダンスを提供します。

ADAL と MSAL アプリの機能の違い

サインインできるユーザー

  • ADAL では、職場と学校のアカウント (Microsoft Entra アカウントとも呼ばれます) のみがサポートされます。
  • MSAL では、Hotmail.com、Outlook.com、Live.com などの個人用Microsoft アカウント (MSA アカウント) がサポートされます。
  • MSAL では、職場と学校のアカウント、および AD B2C アカウントAzureがサポートされています。

標準へのコンプライアンス

  • Microsoft ID プラットフォームは、OAuth 2.0 と OpenId Connect の標準に従います。
  • Microsoft ID プラットフォームを使用すると、アクセス許可を動的に要求できます。 アプリは必要に応じてのみアクセス許可を要求し、アプリで必要に応じてさらに要求できます。 詳細については、「 アクセス許可と同意」を参照してください。

ADAL ライブラリと MSAL ライブラリの違い

MSAL パブリック API には、Azure AD v1.0 とMicrosoft ID プラットフォームの主な違いがいくつか反映されています。

ADAuthenticationContext の代わりに MSALPublicClientApplication

ADAuthenticationContext は、ADAL アプリが作成する最初のオブジェクトです。 これは、ADAL のインスタンス化を表します。 アプリは、Microsoft Entraクラウドとテナント (機関) の組み合わせごとに、ADAuthenticationContextの新しいインスタンスを作成します。 同じ ADAuthenticationContext を使用して、複数のパブリック クライアント アプリケーションのトークンを取得できます。

MSAL では、主な相互作用は、MSALPublicClientApplicationの後にモデル化された オブジェクトを介して行われます。 MSALPublicClientApplicationの 1 つのインスタンスを使用して、複数のMicrosoft Entra クラウドとテナントを操作できます。各機関に新しいインスタンスを作成する必要はありません。 ほとんどのアプリでは、1 つの MSALPublicClientApplication インスタンスで十分です。

リソースではなくスコープ

ADAL では、アプリは、Azure AD v1.0 エンドポイントからトークンを取得するために、などのhttps://graph.microsoft.com識別子を提供する必要がありました。 リソースでは、アプリ マニフェストで認識されるスコープ (oAuth2Permissions) を多数定義できます。 これにより、クライアント アプリは、アプリの登録時に事前に定義された特定のスコープ セットに対して、そのリソースからトークンを要求することができました。

MSAL では、単一のリソース識別子ではなく、アプリは要求ごとに一連のスコープを提供します。 スコープは、resource/permission 形式で、リソース識別子の後にアクセス許可名が続くものです。 たとえば、https://graph.microsoft.com/user.read のように指定します。

MSAL でスコープを提供するには、次の 2 つの方法があります。

  • アプリに必要なすべてのアクセス許可の一覧を指定します。 例えば次が挙げられます。

    @[@"https://graph.microsoft.com/directory.read", @"https://graph.microsoft.com/directory.write"]

    この場合、アプリは directory.readdirectory.write のアクセス許可を要求します。 ユーザーは、このアプリでそれらの権限に以前同意していない場合、それらの権限への同意を求められます。 アプリケーションは、ユーザーがアプリケーションに対して既に同意している追加のアクセス許可を受け取る場合もあります。 ユーザーは、新しいアクセス許可または付与されていないアクセス許可に対してのみ同意を求められます。

  • その /.default スコープ。

これは、すべてのアプリケーションの組み込みスコープです。 これは、アプリケーションの登録時に構成されたアクセス許可の静的リストを参照します。 その動作は、 resourceの動作と似ています。 これは、同様のスコープとユーザー エクスペリエンスのセットが維持されるように移行する場合に役立ちます。

/.default スコープを使用するには、リソース識別子に/.defaultを追加します。 たとえば、 https://graph.microsoft.com/.defaultと指定します。 リソースがスラッシュ (/) で終わる場合でも、先頭のフォワード スラッシュを含む /.default を追加する必要があります。その結果、スコープ内に二重フォワード スラッシュ (//) が含まれることになります。

アクセス許可とスコープで "/.default" スコープを使用する方法の詳細を確認できます。

さまざまな WebView の種類とブラウザーのサポート

ADAL では、iOS の場合は UIWebView/WKWebView、macOS の場合は WebView のみがサポートされます。 iOS 用 MSAL では、承認コードを要求するときに Web コンテンツを表示するためのより多くのオプションがサポートされ、ユーザー エクスペリエンスとセキュリティを向上させる UIWebViewはサポートされなくなりました。

既定では、iOS 上の MSAL では ASWebAuthenticationSession が使用されます。これは、Apple が iOS 12 以降のデバイスでの認証に推奨する Web コンポーネントです。 アプリと Safari ブラウザー間で Cookie を共有することで、シングル サインオン (SSO) の利点が得られます。

アプリの要件と必要なエンドユーザー エクスペリエンスに応じて、異なる Web コンポーネントを使用することを選択できます。 その他のオプションについては、 サポートされている Web ビューの種類 を参照してください。

ADAL から MSAL に移行する場合、 WKWebView は iOS および macOS 上の ADAL と最も似たユーザー エクスペリエンスを提供します。 可能であれば、iOS の ASWebAuthenticationSession に移行することをお勧めします。 macOS の場合は、 WKWebViewを使用することをお勧めします。

アカウント管理 API の相違点

ADAL メソッドacquireToken()またはacquireTokenSilent()を呼び出すと、認証されるアカウントを表すADUserInformationからの要求の一覧を含むid_token オブジェクトを受け取ります。 さらに、ADUserInformationは、userId要求に基づいてupnを返します。 最初の対話型トークンの取得後、ADAL は開発者がすべてのサイレント呼び出しで userId を提供することを想定しています。

ADAL には、既知のユーザー ID を取得するための API は用意されていません。 これらのアカウントの保存と管理は、アプリに依存します。

MSAL には、トークンを取得しなくても MSAL に知られているすべてのアカウントを一覧表示する一連の API が用意されています。

ADAL と同様に、MSAL は、 id_tokenからの要求の一覧を保持するアカウント情報を返します。 これは、MSALAccount オブジェクト内のMSALResult オブジェクトの一部です。

MSAL には、アカウントを削除するための一連の API が用意されており、削除されたアカウントはアプリからアクセスできなくなります。 アカウントが削除されると、後でトークン取得の呼び出しが行われると、対話型のトークン取得を実行するようにユーザーに求められます。 アカウントの削除は、アカウントを起動したクライアント アプリケーションにのみ適用され、デバイスまたはシステム ブラウザーで実行されている他のアプリからアカウントが削除されることはありません。 これにより、個々のアプリからサインアウトした後でも、ユーザーがデバイスで SSO エクスペリエンスを維持し続けられます。

さらに、MSAL は、後でトークンを暗黙的に要求するために使用できるアカウント識別子も返します。 ただし、アカウント識別子 (identifier オブジェクトの MSALAccount プロパティを介してアクセス可能) は表示できず、どの形式にあるか想定することも、解釈や解析を試みる必要もありません。

アカウント キャッシュの移行

ADAL から移行する場合、アプリは通常、MSAL で必要なuserIdを持たない ADAL のidentifierを格納します。 1 回限りの移行手順として、アプリは次の API で ADAL の userId を使用して MSAL アカウントに対してクエリを実行できます。

- (nullable MSALAccount *)accountForUsername:(nonnull NSString *)username error:(NSError * _Nullable __autoreleasing * _Nullable)error;

この API は、MSAL と ADAL の両方のキャッシュを読み取り、ADAL userId (UPN) によってアカウントを検索します。

アカウントが見つかった場合、開発者はアカウントを使用してサイレント トークンの取得を行う必要があります。 最初のサイレント トークンの取得では、アカウントが効果的にアップグレードされ、開発者は MSAL の結果 (identifier) で MSAL と互換性のあるアカウント識別子を取得します。 その後、次の API を使用して、アカウント参照に identifier のみを使用する必要があります。

- (nullable MSALAccount *)accountForIdentifier:(nonnull NSString *)identifier error:(NSError * _Nullable __autoreleasing * _Nullable)error;

MSAL のすべての操作で ADAL の userId を引き続き使用することは可能ですが、userId は UPN に基づいているため、ユーザー エクスペリエンスの低下につながる複数の制限を受けます。 たとえば、UPN が変更された場合、ユーザーはもう一度サインインする必要があります。 すべてのアプリで、すべての操作に表示できないアカウント identifier を使用することをお勧めします。

キャッシュ状態の移行について詳しくは、こちらをご覧ください。

トークン取得の変更

MSAL では、いくつかのトークン取得呼び出しの変更が導入されています。

  • ADAL と同様に、 acquireTokenSilent は常にサイレント要求になります。
  • ADAL とは異なり、acquireTokenは常に、Web ビューまたはMicrosoft Authenticator アプリを介してユーザーが操作可能な UI になります。 webview/Microsoft Authenticator 内の SSO の状態によっては、ユーザーに資格情報の入力を求められる場合があります。
  • ADAL では、acquireTokenを使用したAD_PROMPT_AUTOは最初にサイレント トークンの取得を試み、サイレント要求が失敗した場合にのみ UI を表示します。 MSAL では、このロジックを実現するには、最初に acquireTokenSilent を呼び出し、サイレント取得が失敗した場合にのみ acquireToken を呼び出します。 これにより、開発者は対話型トークンの取得を開始する前にユーザー エクスペリエンスをカスタマイズできます。

エラー処理の違い

MSAL を使用すると、アプリで処理できるエラーと、ユーザーによる介入が必要なエラーの間でより明確になります。 開発者が処理する必要があるエラーの数は限られています。

  • MSALErrorInteractionRequired: ユーザーは対話型の要求を行う必要があります。 これは、認証セッションの期限切れ、条件付きアクセス ポリシーの変更、更新トークンの有効期限が切れた、取り消された、キャッシュに有効なトークンがないなどのさまざまな理由で発生する可能性があります。
  • MSALErrorServerDeclinedScopes: 要求が完全に完了せず、一部のスコープにアクセス権が付与されませんでした。 これは、ユーザーが 1 つ以上のスコープへの同意を拒否した場合に発生する可能性があります。

MSALError リスト内の他のすべてのエラーの処理は省略可能です。 これらのエラーの情報を使用して、ユーザー エクスペリエンスを向上させることができます。

MSAL エラー処理の詳細については、MSAL を使用した例外とエラーの処理を参照してください。

ブローカーのサポート

バージョン 0.3.0 以降の MSAL では、Microsoft Authenticator アプリを使用したブローカー認証のサポートが提供されます。 Microsoft Authenticatorでは、条件付きアクセス シナリオのサポートも有効になります。 条件付きアクセスシナリオの例としては、ユーザーが Intune を介してデバイスを登録するか、トークンを取得するためにMicrosoft Entra IDに登録する必要があるデバイス コンプライアンス ポリシーが含まれます。 モバイル アプリケーション管理 (MAM) 条件付きアクセス ポリシー。アプリでトークンを取得するには、コンプライアンスの証明が必要です。

アプリケーションのブローカーを有効にするには:

  1. アプリケーションのブローカー互換リダイレクト URI 形式を登録します。 ブローカー互換のリダイレクト URI 形式が msauth.<app.bundle.id>://auth<app.bundle.id>をアプリケーションのバンドル ID に置き換えます。 ADAL から移行していて、アプリケーションが既にブローカー対応であった場合は、追加の操作は必要ありません。 前のリダイレクト URI は MSAL と完全に互換性があるため、手順 3 に進むことができます。

  2. アプリケーションのリダイレクト URI スキームを info.plist ファイルに追加します。 既定の MSAL リダイレクト URI の場合、形式は msauth.<app.bundle.id>。 例えば次が挙げられます。

    <key>CFBundleURLSchemes</key>
    <array>
        <string>msauth.<app.bundle.id></string>
    </array>
    
  3. 次のスキームをアプリの Info.plist の LSApplicationQueriesSchemes の下に追加します。

    <key>LSApplicationQueriesSchemes</key>
    <array>
         <string>msauthv2</string>
         <string>msauthv3</string>
    </array>
    
  4. コールバックを処理するために AppDelegate.m ファイルに次のコードを追加します:Objective-C:

    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options`
    {
        return [MSALPublicClientApplication handleMSALResponse:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]];
    }
    

    Swift:

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String)
    }
    

企業間 (B2B)

ADAL では、アプリがトークンを要求するテナントごとに、 ADAuthenticationContext の個別のインスタンスを作成します。 これは MSAL での要件ではなくなりました。 MSAL では、acquireToken 呼び出しと acquireTokenSilent 呼び出しに対して別の権限を指定することで、MSALPublicClientApplicationの単一のインスタンスを作成し、任意のMicrosoft Entraクラウドおよび組織に使用できます。

他の SDK とのパートナーシップでの SSO

iOS 用 MSAL では、ADAL Objective-C 2.7.x 以降で統合キャッシュを介して SSO を実現できます。

SSO は iOS キーチェーン共有を介して実現され、同じ Apple Developer アカウントから発行されたアプリ間でのみ使用できます。

iOS キーチェーン共有を介した SSO は、唯一のサイレント SSO の種類です。

macOS では、MSAL は iOS および macOS ベースのアプリケーションと ADAL Objective-C ベースのアプリケーション用の他の MSAL との SSO を実現できます。

iOS 上の MSAL では、他の 2 種類の SSO もサポートされています。

  • Web ブラウザーを使用した SSO。 MSAL for iOS では、 ASWebAuthenticationSessionがサポートされています。これにより、デバイス上の他のアプリと特に Safari ブラウザー間で共有される Cookie を介して SSO が提供されます。
  • 認証ブローカーを介した SSO。 iOS デバイスでは、Microsoft Authenticatorは認証ブローカーとして機能します。 準拠デバイスの要求などの条件付きアクセス ポリシーに従い、登録済みデバイスに SSO を提供できます。 バージョン 0.3.0 以降の MSAL SDK では、既定でブローカーがサポートされています。

Intune MAM SDK

Intune MAM SDK では、バージョン 11.1.2 以降の iOS 用 MSAL がサポートされます

同じアプリ内の MSAL と ADAL

ADAL バージョン 2.7.0 以降は、同じアプリケーションで MSAL と共存できません。 主な理由は、共有サブモジュールの共通コードが原因です。 Objective-C は名前空間をサポートしていないため、ADAL フレームワークと MSAL フレームワークの両方をアプリケーションに追加すると、同じクラスのインスタンスが 2 つ存在します。 実行時に選択される保証はありません。 両方の SDK が競合するクラスの同じバージョンを使用している場合でも、アプリが動作する可能性があります。 ただし、別のバージョンの場合は、診断が困難な予期しないクラッシュがアプリで発生する可能性があります。

同じ運用アプリケーションでの ADAL と MSAL の実行はサポートされていません。 ただし、ユーザーをテストして ADAL Objective-C から MSAL for iOS および macOS に移行するだけの場合は、引き続き ADAL Objective-C 2.6.10 を使用できます。 同じアプリケーションで MSAL で動作する唯一のバージョンです。 この ADAL バージョンの新機能の更新プログラムは存在しないため、移行とテストの目的でのみ使用する必要があります。 アプリが ADAL と MSAL の共存に長期的に依存しないようにする必要があります。

同じアプリケーションでの ADAL と MSAL の共存はサポートされていません。 複数のアプリケーション間の ADAL と MSAL の共存は完全にサポートされています。

実際の移行手順

アプリ登録の移行

MSAL に切り替えてMicrosoft Entra アカウントを有効にするために、既存のMicrosoft Entra アプリケーションを変更する必要はありません。 ただし、ADAL ベースのアプリケーションがブローカー認証をサポートしていない場合は、MSAL に切り替える前に、アプリケーションの新しいリダイレクト URI を登録する必要があります。

リダイレクト URI は、次の形式にする必要があります: msauth.<app.bundle.id>://auth<app.bundle.id>をアプリケーションのバンドル ID に置き換えます。 Microsoft Entra 管理センターでリダイレクト URI を指定します

iOS の場合のみ、証明書ベースの認証をサポートするには、追加のリダイレクト URI をアプリケーションに登録し、Microsoft Entra 管理センターを次の形式で登録する必要があります: msauth://code/<broker-redirect-uri-in-url-encoded-form>。 たとえば、msauth://code/msauth.com.microsoft.mybundleId%3A%2F%2Fauth のように指定します。

すべてのアプリで両方のリダイレクト URI を登録することをお勧めします。

増分同意のサポートを追加する場合は、[API アクセス許可] タブのアプリ登録で、アプリがアクセスを要求するように構成されている API と アクセス許可 を選択します。

ADAL から移行していて、Microsoft Entra ID アカウントと MSA アカウントの両方をサポートする場合は、両方をサポートするように既存のアプリケーション登録を更新する必要があります。 Microsoft Entra IDと MSA の両方をすぐにサポートするように既存の運用アプリを更新することはお勧めしません。 代わりに、テスト用にMicrosoft Entra IDと MSA の両方をサポートする別のクライアント ID を作成し、すべてのシナリオが動作することを確認したら、既存のアプリを更新します。

アプリに MSAL を追加する

任意のパッケージ管理ツールを使用して、MSAL SDK をアプリに追加できます。 詳細な手順については、こちらをご覧ください

アプリの Info.plist ファイルを更新する

iOS の場合のみ、アプリケーションのリダイレクト URI スキームを info.plist ファイルに追加します。 ADAL ブローカーと互換性のあるアプリの場合は、既に存在している必要があります。 既定の MSAL リダイレクト URI スキームは、 msauth.<app.bundle.id>形式になります。

<key>CFBundleURLSchemes</key>
<array>
    <string>msauth.<app.bundle.id></string>
</array>

アプリの Info.plist の LSApplicationQueriesSchemesの下に、次のスキームを追加します。

<key>LSApplicationQueriesSchemes</key>
<array>
     <string>msauthv2</string>
     <string>msauthv3</string>
</array>

AppDelegate コードを更新する

iOS の場合のみ、AppDelegate.m ファイルに次のコードを追加します。

Objective-C:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options`
{
    return [MSALPublicClientApplication handleMSALResponse:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]];
}

Swift:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String)
}

Xcode 11 を使用している場合は、代わりに MSAL コールバックを SceneDelegate ファイルに配置する必要があります。 以前の iOS との互換性を保持するために UISceneDelegate と UIApplicationDelegate の両方をサポートしている場合は、MSAL コールバックを両方のファイルに配置する必要があります。

Objective-C:

 - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
 {
     UIOpenURLContext *context = URLContexts.anyObject;
     NSURL *url = context.URL;
     NSString *sourceApplication = context.options.sourceApplication;
     
     [MSALPublicClientApplication handleMSALResponse:url sourceApplication:sourceApplication];
 }

Swift:

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        
        guard let urlContext = URLContexts.first else {
            return
        }
        
        let url = urlContext.url
        let sourceApp = urlContext.options.sourceApplication
        
        MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: sourceApp)
    }

これにより、MSAL はブローカーと Web コンポーネントからの応答を処理できます。 ADAL では、アプリ デリゲート メソッドが自動的に "スウィズル" されるため、これは必要ありませんでした。 手動で追加するとエラーが発生しにくく、アプリケーションの制御が増えます。

トークンのキャッシュの有効化

既定では、MSAL はアプリのトークンを iOS または macOS キーチェーンにキャッシュします。

トークン キャッシュを有効にするには:

  1. アプリケーションが正しく署名されていることを確認する
  2. Xcode Project設定 >Capabilities タブに移動します>キーチェーン共有を有効にする
  3. [ + ] をクリックし、次の キーチェーン グループ エントリを入力します。3.a iOS の場合は、「 com.microsoft.adalcache 3.b For macOS」と入力します。 com.microsoft.identity.universalstorage

MSALPublicClientApplication を作成し、その acquireToken 呼び出しと acquireTokeSilent 呼び出しに切り替えます

次のコードを使用して MSALPublicClientApplication を作成できます。

Objective-C:

NSError *error = nil;
MSALPublicClientApplicationConfig *configuration = [[MSALPublicClientApplicationConfig alloc] initWithClientId:@"<your-client-id-here>"];
    
MSALPublicClientApplication *application =
[[MSALPublicClientApplication alloc] initWithConfiguration:configuration
                                                     error:&error];

Swift:

let config = MSALPublicClientApplicationConfig(clientId: "<your-client-id-here>")
do {
  let application = try MSALPublicClientApplication(configuration: config)
  // continue on with application
            
} catch let error as NSError {
  // handle error here
}

次に、アカウント管理 API を呼び出して、キャッシュにアカウントがあるかどうかを確認します。

Objective-C:

NSString *accountIdentifier = nil /*previously saved MSAL account identifier */;
NSError *error = nil;
MSALAccount *account = [application accountForIdentifier:accountIdentifier error:&error];

Swift:

// definitions that need to be initialized
let application: MSALPublicClientApplication!
let accountIdentifier: String! /*previously saved MSAL account identifier */

do {
  let account = try application.account(forIdentifier: accountIdentifier)
  // continue with account usage
} catch let error as NSError {
  // handle error here
}

または、すべてのアカウント情報を読み取る:

Objective-C:

NSError *error = nil;
NSArray<MSALAccount *> *accounts = [application allAccounts:&error];

Swift:

let application: MSALPublicClientApplication!
do {
  let accounts = try application.allAccounts()
  // continue with account usage
} catch let error as NSError {
  // handle error here
}

アカウントが見つかった場合は、MSAL acquireTokenSilent API を呼び出します。

Objective-C:

MSALSilentTokenParameters *silentParameters = [[MSALSilentTokenParameters alloc] initWithScopes:@[@"<your-resource-here>/.default"] account:account];
    
[application acquireTokenSilentWithParameters:silentParameters
                              completionBlock:^(MSALResult *result, NSError *error)
{
    if (result)
    {
        NSString *accessToken = result.accessToken;
        // Use your token
    }
    else
    {
        // Check the error
        if ([error.domain isEqual:MSALErrorDomain] && error.code == MSALErrorInteractionRequired)
        {
            // Interactive auth will be required
        }
            
        // Other errors may require trying again later, or reporting authentication problems to the user
    }
}];

Swift:

let application: MSALPublicClientApplication!
let account: MSALAccount!
        
let silentParameters = MSALSilentTokenParameters(scopes: ["<your-resource-here>/.default"], 
                                                 account: account)
application.acquireTokenSilent(with: silentParameters) {
  (result: MSALResult?, error: Error?) in
  if let accessToken = result?.accessToken {
     // use accessToken
  }
  else {
    // Check the error
    guard let error = error else {
      assert(true, "callback should contain a valid result or error")
      return
    }
    
    let nsError = error as NSError
    if (nsError.domain == MSALErrorDomain
        && nsError.code == MSALError.interactionRequired.rawValue) {
      // Interactive auth will be required
    }
                
    // Other errors may require trying again later, or reporting authentication problems to the user
  }
}

次のステップ

認証フローとアプリケーション シナリオの詳細を確認する