コンソールアプリで Azure AD B2C の ROPC フローを試してみた
コンソールアプリを使って Azure AD B2C の ROPC フローを試してみました。
ROPC フローとは
Azure AD B2C は通常はブラウザや WebView を介してサインインをおこないますが、ROPC フローはユーザー名とパスワードを直接処理することでユーザーをサインインすることができるフローです。
また、ROPC フローは作成済みのユーザー情報を使ってサインイン、サインアウトをすることはできますが、新規のユーザーを作成することはできません。
ROPC フローはサインイン時にユーザー情報がむき出しになるなどの理由から非推奨となっているので注意が必要です。
ROPC フローは "使用しない" ことをお勧めします。 ほとんどのシナリオでは、より安全な代替手段を利用でき、推奨されます。 このフローでは、アプリケーションで非常に高い信頼度が要求されるため、他のフローには存在しないリスクが伴います。 他のもっと安全なフローを使用できない場合にのみ、このフローを使用する必要があります。
(補足)コンソールアプリではブラウザを使った認証を使用できない(ただし Graph API の場合使用可能)
Azure AD B2C のコンソールアプリは http://localhost
の URI へリダイレクトされるため、ブラウザや WebView を使用する(「サインアップとサインイン」などの)フローの認証を使用することができません。
アプリの登録時に、http://localhost をリダイレクト URI として構成します (B2C では現在サポートされていません)
同じ MSAL ライブラリを使う場合でも、Graph API の場合はブラウザによる認証を使ったコンソールアプリを作成することができます。
Azure AD B2C の環境を作成する
ということで、Azure AD B2C で ROPC フローの環境を作成していきます。
Azure AD B2C のテナントを作成する
(ドキュメントにある Azure AD B2C を先に作るより)リソースグループから Azure AD B2C テナントを作成すると、テナントへのリンクが減る分少し簡単になります。
アプリの登録をする
作成した Azure AD B2C テナントから「アプリの登録」→「新規登録」でアプリケーションを登録します。
「認証」を選択して、「アクセス トークン (暗黙的なフローに使用)」にチェック、「パブリック クライアント フローを許可する」で「はい」を選択します。
ユーザーフローを作成する
テナントに戻り、「新しいユーザーフフロー」→「リソース所有者のパスワード資格情報 (ROPC) を使用してサインインする」→「作成」を選択します。
ユーザーフロー名を入力して「作成」を選択します。
MSAL で使う場合は、( Scope にアプリケーションIDを指定する方法が機能しないので)API を公開してスコープに追加する必要があるのでさらに下記を実行します。
「API の公開」→「Scope の追加」→「スコープ名」を
api
(任意)で「スコープの追加」を選択「"組織"名に管理者の同意を与えます」→「はい」
(C# で使用するときに)スコープ名に https://{テナント名}.onmicrosoft.com/{クライアントID}/{作成したスコープ名}
を追加します。
The identifier URI of the backend App Registration and scope name are concatenated to obtain the scope used in the next step.
C# で動かしてみる
C# と MSAL ライブラリーを使用して、作成した ROPC フローでサインインをおこないます。 今回のサンプルはこちらに置いてあります。 github.com
入力画面に Sharpromt を使う
メールアドレスとパスワードの入力は @shibayan の Sharpromt
を使ってます。Sharpromt
を使うと簡単にコンソールでの入力画面を作成できます。
var username = Sharprompt.Prompt.Input<string>("Input username."); var password = Sharprompt.Prompt.Password("Input password.");
実行するとこんな感じです。
MSAL を使ってサインインを実装する
MSAL ライブラリーを使ってサインインをする場合、
Azure AD B2C の情報から
IPublicClientApplication
を作成するキャッシュがあればそっちでログインを試みる
キャッシュがなければ(例外が発生するので)通常でログインする
成功すれば、
AuthenticationResult
からid token
を取得する
という流れになります。
App = PublicClientApplicationBuilder.Create(ClientID) .WithB2CAuthority(Authority) .Build(); AuthenticationResult result = null; var accounts = await App.GetAccountsAsync(); var account = accounts.FirstOrDefault(); try { result = await App.AcquireTokenSilent(Scopes, account) .ExecuteAsync(); } catch (MsalUiRequiredException) { try { result = await App.AcquireTokenByUsernamePassword(Scopes, username, passwordText) .ExecuteAsync(); } catch (MsalUiRequiredException ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(ex.Message); Console.ResetColor(); } catch (MsalUiRequiredException ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(ex.Message); Console.ResetColor(); } // AADB2C90225: The username or password provided in the request are invalid. catch (MsalServiceException ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(ex.Message); Console.ResetColor(); } } // Cache empty or no token for account in the cache, attempt by username/password if (result != null) { Console.WriteLine($"IdToken: {result.IdToken}"); Console.WriteLine($"AccessToken: {result.AccessToken}"); }
実行結果です。
ちなみに、サンプルを実行して Fiddler で覗いて見るとがっつりパスワードが見えちゃってます(のでくれぐれもご注意ください)。
最後に
ROPC フローはあくまでも非推奨となっていますが、この仕様を理解した上で手軽に社内ツールを作成する場合などは有効な方法かなと思います。
それでは、Happy B2C Life!!