- 2021年2月17日
この記事について
IB-Mesの利用者登録周りでMicrosoft Graph APIに触る機会があったので、ざっくりとした使い方やハマったところなどを備忘録もかねて書いておきます。
Microsoft Graph とは
マイクロソフトによると、
「Microsoft Graph は、Microsoft 365 のデータとインテリジェンスへの入り口です。」
とのことなので、おそらくMicrosoft 365みたいなサービスに対する共通のインターフェースを提供してくれるもののようです(たぶん(きっと。
ということで今回はこいつを使ってAzure AD B2Cへのアクセスとユーザーの登録までをやってみます。
準備
Microsoft Graph のインストール
必要なパッケージをNuGetでインストールします。
インストールするパッケージは以下になります。
Microsoft.Identity.ClientはAzure ADへのアクセス時に使用するトークンの取得などをやってくれるライブラリ(MSAL: Microsoft Authentication Library)です。
コード
ではコードを見ていきます。
流れとしては、
- GraphServiceClientの作成
- ユーザーの登録
というシンプルなものになります。
GraphServiceClientの作成
using Microsoft.Graph; using Microsoft.Graph.Auth; using Microsoft.Identity.Client; namespace HelloGraph { class Program { public static async Task Main(string[] args) { var tenantId = "{my-tenant-id}"; var clientId = "{my-app-client-id}"; var clientSecret = "{my-app-client-secret}"; var client = ConfidentialClientApplicationBuilder .Create(clientId) .WithTenantId(tenantId) .WithClientSecret(clientSecret) .Build(); var authProvider = new ClientCredentialProvider(client); var graphClient = new GraphServiceClient(authProvider); ... } } }
まずConfidentialClientApplicationBuilder.Build()
を利用してConfidentialClientApplication
のインスタンスを作成します。
このときAzureにおけるアプリの、テナントID・クライアントID・クライアントシークレットを指定します。
※これらの値は秘匿の情報になるため、実際のコードに含めずセキュアな方法で管理することをおすすめします。(Azure Key Vaultはいいものだ)
作成したConfidentialClientApplication
を基にClientCredentialProvider
を作成し、これをGraphServiceClient
のコンストラクタに渡して初期化します。簡単ですね。
ユーザーの登録
using Microsoft.Graph; using Microsoft.Graph.Auth; using Microsoft.Identity.Client; namespace HelloGraph { class Program { public static Task Main(string[] args) { ... var userName = "ユニフェイス太郎"; var userPassword = "PASUWADO1234"; var userEmail = "taro_uniface@outlook.com"; var tenant = "{my-tenant}.onmicrosoft.com"; // 登録ユーザー作成 var userToAdd = new Microsoft.Graph.User { AccountEnabled = true, DisplayName = userName, PasswordProfile = new PasswordProfile { Password = userPassword, ForceChangePasswordNextSignIn = false, ForceChangePasswordNextSignInWithMfa = false, }, Mail = userEmail, MailNickname = Guid.NewGuid().ToString(), Identities = new [] { new ObjectIdentity { Issuer = tenant, IssuerAssignedId = userEmail, SignInType = "emailAddress" }, }, UserPrincipalName = $"{mailNickname}@{tenant}", }; // 登録 var user = await graphClient.Users.Request().AddAsync(userToAdd); ... } } }
パッと見ややこいですが、User
オブジェクトを作成してGraphServiceClient.Users.Request().AddAsync()
に渡しているだけです。
ユーザーの登録自体はこれだけで完了なのですが、User
オブジェクト作成時のパラメータが複雑なので少し説明します。
Microsoft.Graph.User オブジェクト
Microsoft.Graph.User
オブジェクトは単なるDTOです。
なのでほとんどのプロパティにはセッターが用意されているのですが、一部のプロパティは読み取り専用となっているので注意が必要です。
読み取り専用のプロパティに書き込もうした場合、コンパイルは通りますが実行時にエラーになります。
どのプロパティが読み取り専用かはプロパティのコメントにRead-onlyの記載があります。
また、一部のプロパティは必須となっており、設定しなかった場合にも実行時(AddAsync()
によるPOST時)にエラーが発生します。
以下のプロパティは設定が必須になっています(他にもあるかも)。
- AccountEnabled
- PasswordProfile
- MailNickname
- UserPrincipalName
また、プロパティの中には正しく設定しないとエラーになったりサインインできなくなるものがあるため、ハマりやすい(実際にハマった)ものについて説明します。
MailNickname プロパティ
ユーザー登録のコードではMailNickname
に新規のGUIDを設定していますが、これはAzure Portalからユーザーを登録した際に適当なGUIDが振られるためその挙動に合わせています。
プロパティの名前に従うのであればメールアドレスの一部を設定するのが良いかもしれません。
また一部の記号は使用できないなどの制約があります。
Identities プロパティ
Identities
プロパティに設定するObjectIdentity
オブジェクトはサインイン時に要求する情報に応じて正しく初期化する必要があります。
このプロパティの内容に問題があると作成したユーザーでサインインできないなどの問題が発生するので注意が必要です。
このプロパティは省略可能ですが、省略した場合はUserPrincipalName
に設定した文字列でサインインすることができます。
注意点として、読み違えてなければObjectIdentity.IssuerAssignedId
プロパティのコメントには「SignInType
が"mailAddress"
の時はメールアドレスのローカル部(@
より前)を設定しろ」みたいなことが書いてありますが@
以降も含めて設定しないとエラーになります。
メールアドレスによるサインインとユーザー名によるサインインのObjectIdentity
の初期化コードを載せておきます。
・メールアドレスでサインインする場合
new ObjectIdentity { Issuer = tenant, IssuerAssignedId = "taro_uniface@example.com", SignInType = "emailAddress" }
・ユーザー名でサインインする場合
new ObjectIdentity { Issuer = tenant, IssuerAssignedId = "taro uniface", SignInType = "userName" }
UserPrincipalName プロパティ
UserPrincipalName
プロパティですが、Azure AD B2Cでは***@{tenant}.onmicrosoft.com
の形式で設定する必要があります。{tenant}
の部分はテナントIDではなくテナント名になるので注意。
余談ですが、先のIdentities
を省略するとUserPrincipalName
が使用されるため、当初ここにユーザーのメールアドレスを指定すればよいのでは?と思ったのですがダメでした(全くサインインできずに悩みました)。
なので、少なくともAzure AD B2Cを利用する場合にはIdentities
プロパティを正しく設定しないとメールアドレスによるサインインが行えないことになります。
ちなみにローカルのADからユーザーを連携した場合はUserPrincipalName
には{user-email}#EXT#@{tenant}.onmicrosoft.com
のような文字列が設定されます。
おまけ
カスタム属性の付与
おまけとしてカスタム属性の設定についても書いておきます。
var user = new Microsoft.Graph.User { AdditionalData = new Dictionary<string, object> { { GetCompleteKey("my-attribute-1"), "foobar" }, { GetCompleteKey("my-attribute-2"), 123 } ... }, ... }; // キーの作成 string GetCompleteKey(string key) { var extAppClientId = "{ext-app-client-id}"; return $"extension_{extAppClientId.Replace("-", string.Empty)}_{key}"; }
AdditionalData
プロパティにDictionary
を渡してやることでユーザーに任意の属性を設定することができます。
Azure AD B2Cに登録する場合にはキーの形式に決まりがあるため、上記のコードではGetCompleteKey()
メソッドで正しい形式のキーを作成しています。その部分について少し説明します。
キーの作成
まず、extAppClientId
ですが、Azure AD B2Cに登録されたb2c-extensions-app
アプリのクライアントIDになります。b2c-extensions-app
アプリはAzure上でAD B2Cのリソースを作成すると自動で作成されます。
Azure AD B2Cに登録する際のカスタム属性のキーには、このクライアントIDを含める必要があります。
実際にプロパティに設定する際のキーはextension_{ext-app-client-id}_{attribute-key}
といった形式になります。
クライアントIDはGUIDですが、キーに-(ハイフン)
を含めることはできないのでキーとして含める際に-(ハイフン)
を取り除く必要があります。
まとめ
今回はMicrosoft Graph APIを利用してAzure AD B2Cにユーザーを登録するまでを書いてみました。
細かいところでハマりどころがありますが、Graph API自体は結構簡単に操作できる印象でした。
正直、ガシガシGraph APIを使ってコードを書いていくことは少ないかと思いますが、今後誰かが(自分が)触る時に同じ罠にハマらないための轍として役立ててもらえたらと思います。