Salesforceの認証プロセスとして独自のログインフローを構築できるということで、Salesforceが公開しているパッケージを使って以下のチュートリアル(TOTPの2要素認証)に従って検証してみました。 Login-Flows - developer.force.com
ログインフローは以下のスライドも詳しいです! 7 power night2014_kanbayashi
ログインフローについて
ログインフローを利用することで、Flow Designerで作成したフローとログイン時の処理を紐付けることができ、通常の認証処理が完了した後、Flow Designerで作成したフローを走らせることができます。Flow DesignerではフローのプラグインをApexを使って作成でき、このプラグインからTOTP用のQRコード生成やバリデーションをすることが可能なので、独自の多要素認証の認証フローを作成することができます。
パッケージのインストール
まずは以下のパッケージをSalesforce環境にインストールします。 https://login.salesforce.com/packaging/installPackage.apexp?p0=04to0000000WA6J
フローの作成
設定>作成>ワークフローと承認申請>フロー でインストールしたフロー一覧が表示されるので、SF-TOTPの[オープン]をクリックします。
こんな感じでフローが表示されます。
TOTPPluginのクラスは以下の内容となっています。
global class TOTPPlugin implements Process.Plugin
{
global Process.PluginDescribeResult describe()
{
Process.PluginDescribeResult result = new Process.PluginDescribeResult();
result.description='This plug-in handles salesforce standard two factor authentication methods.';
result.tag='Identity';
result.inputParameters = new List<Process.PluginDescribeResult.InputParameter> {
new Process.PluginDescribeResult.InputParameter('OTP_INPUT', Process.PluginDescribeResult.ParameterType.STRING, true),
new Process.PluginDescribeResult.InputParameter('OTP_REGISTRATION_INPUT', Process.PluginDescribeResult.ParameterType.STRING, true),
new Process.PluginDescribeResult.InputParameter('SECRET_INPUT', Process.PluginDescribeResult.ParameterType.STRING, true)
};
result.outputParameters = new List<Process.PluginDescribeResult.OutputParameter> {
new Process.PluginDescribeResult.OutputParameter('QR_URL_OUTPUT',
Process.PluginDescribeResult.ParameterType.STRING),
new Process.PluginDescribeResult.OutputParameter('SECRET_OUTPUT',
Process.PluginDescribeResult.ParameterType.STRING),
new Process.PluginDescribeResult.OutputParameter('IsValid_OUTPUT',
Process.PluginDescribeResult.ParameterType.Boolean)
};
return result;
}
global Process.PluginResult invoke(Process.PluginRequest request)
{
Map<String,String> QR;
String URL;
String otp;
String secret;
Boolean status = false;
String userid = UserInfo.getUserId();
Map<String, Object> result = new Map<String, Object>();
List<TwoFactorInfo> twoFactors = [SELECT UserId, Type FROM TwoFactorInfo where userID = :userid];
secret = (String)request.inputParameters.get('SECRET_INPUT');
if(twoFactors.isEmpty() && secret == null)
{
QR = Auth.SessionManagement.getQrCode();
URL = QR.get('qrCodeUrl');
Secret = QR.get('secret');
result.put('QR_URL_OUTPUT', URL);
result.put('SECRET_OUTPUT', Secret);
return new Process.PluginResult(result);
}
otp = (String)request.inputParameters.get('OTP_REGISTRATION_INPUT');
if(otp == null)
otp = (String)request.inputParameters.get('OTP_INPUT');
result.put('IsValid_OUTPUT', validate(otp, secret));
return new Process.PluginResult(result);
}
private Boolean validate(String otp, String secret)
{
String userid = UserInfo.getUserId();
Boolean status = false;
if(secret == null)
{
try {
status = Auth.SessionManagement.validateTotpTokenForUser(otp);
}
catch(Exception e)
{
system.debug('The key is invalid or the current user has attempted too many validations');
}
return status;
}
status = Auth.SessionManagement.validateTotpTokenForKey(secret, otp);
if(status == true)
{
TwoFactorInfo TwoFactor = new TwoFactorInfo(UserId=userid, Type='TOTP', SharedKey=secret);
insert(TwoFactor);
}
return status;
}
}
フローのプラグインを作るにはProcess.Pluginインターフェースを実装する必要があります。実装するメソッドはdescribeとinvokeの2つで、describeメソッドはフローのプラグインの外部仕様、invokeメソッドは実際の処理の内容を定義します。 describeメソッドではProcess.PluginDescribeResultのインスタンスを返します。Process.PluginDescribeResultのインスタンスはフローエレメントの入出力のパラメータを定義することになります。 invokeメソッドではProcess.PluginRequestが引数として渡るので、当インスタンスから入力値を受け取り、Process.PluginResultで出力する値を定義します。invoke内の入出力パラメータはdescribeで定義したものになります。
サンプルのフローの説明をしていきます。 TOTP用のシークレットを発行していない状態では以下の様なフローになります。
- 1つ目のTOTPPluginのフローエレメントでQRコード及びTOTP用のシークレットを発行する。
- secretが発行されているのでRegistrationのエレメントではRegisterのフローを進む。
- Registration Screenの画面では取得したQRコードを表示しつつ、TOTPのテキストボックスを配置する。 →56行目で処理されるOTP_REGISTRATION_INPUTをセット
- 2つ目のTOTPPluginでは入力したTOTPと1で発行したsecretからバリデーションを行う →86行目のAuth.SessionManagement.validateTotpTokenForKey(secret, otp)
TOTP用のシークレットが既に発行されている状態では以下の様なフローになります。
- 1つめのTOTPPluginのエレメントでは何もせずスルー(状態変数が変わるものの、後続に影響なし)
- 1でsecretを発行していないのでRegistrationのエレメントでGet TOTPのフローを進む。
- Get Token Screenの画面ではTOTPのテキストボックスを配置する。 →59行目で処理されるOTP_INPUTをセット
- 2つ目のTOTPPluginでは入力したTOTPを使ってバリデーションを行う。 →76行目のAuth.SessionManagement.validateTotpTokenForUser(otp)
TwoFactorInfoオブジェクトではユーザの2要素認証の共有秘密鍵を管理していて、Auth.SessionManagement.validateTotpTokenForUserではユーザに割り当てられた秘密鍵を使って入力値の検証を行っています。
ログインフローの作成
セキュリティのコントロール>ログインフローに移動します。
[新規]を押下して、こんな感じでプロファイルに紐付けて作成すればOK。
あと、ログインフローを利用する場合は、プロファイルの[ユーザインターフェースログインの 2 要素認証]のチェックを外した方が良いです。私が試した時に2要素認証⇔ログインフローの無限ループになりました。
フローの実行
クレデンシャルによるフォームログイン後、初回はAuth.SessionManagement.getQrCode()で取得したQRコードのURLの画像が埋め込まれたトークンの入力フォームが表示されます。
二回目以降はQRコードの画像は出ません。
2要素認証で作成した共有秘密鍵はTwoFactorInfoに保持されますが、こちらはdelete文で削除できません。削除するにはユーザの詳細画面で時間ベースのトークンの[削除]リンクをクリックすればOKです。
まとめ
今回はTOTPの2要素認証のパターンでしたが、フローではメールを飛ばしたり、色んな条件分岐や画面を組み合わせることが出来るため、柔軟にカスタマイズすることが出来ます。Auth.SessionManagementクラスには組織の信頼済みIP範囲内かどうかを確認するメソッドがあったり、実行時の日時等をPlugin内のApexで取得できるので、リスクベース認証的なことも実装できそうな感じです。