2020年5月20日水曜日

Build速報!FacebookでAzure ADへログインする

こんにちは、富士榮です。

Buildですね。リモート開催になって日本からでも参加しやすくなって睡眠時間が削られる日々をお過ごしだと思います。

今回も色々とAzure ADに関する新機能の発表がありました。

詳しくはAlexのBlogを参照してもらえれば良いのですが、今回はAzure AD B2Bの直接フェデレーションへのFacebookログインの登場(Public Preview)の話です。

AlexのBlog)
https://techcommunity.microsoft.com/t5/azure-active-directory-identity/build-2020-fostering-a-secure-and-trustworthy-app-ecosystem-for/ba-p/1257360

Azure AD B2Bの直接フェデレーションについてはこれまでも取り上げてきましたが、Googleに対応してから約2年、ここに来てFacebookに対応しました。

Googleとの連携の件
https://idmlab.eidentity.jp/2018/08/azure-ad-b2bgoogleid.html

カスタムドメイン vs 直接フェデレーションの件
https://idmlab.eidentity.jp/2020/05/azure-adbyoid.html



もはやB2Bとは?というしかない状態なのですが、早速触ってみましょう。

外部アイデンティティプロバイダの設定

これまでも紹介したことのある画面です。Azure ADのポータルを開き、External Identitiesメニューから「すべてのIDプロバイダー」を開くと「Google」に加えて新たに「Facebook」が選べるようになっています。


ここでFacebookを追加して、AppIdとAppSecretを追加すれば基本は終わりなのですが、設定前に外部ユーザによるサインアップを許可しておく必要があります。
「外部コラボレーションの設定」メニューを見ると、新しく「ユーザーフローによるゲストセルフサービスサインアップを有効にする」という設定が追加されているので有効にしておきましょう。

後はFacebook Developerコンソールで作成したアプリケーションのAppIdとAppSecretを設定すれば終わりです。

Facebook側の設定方法はAzure AD B2Cのドキュメントに記載がありますので、1点を除いてこちらをそのまま使って大丈夫です。
https://docs.microsoft.com/en-us/azure/active-directory-b2c/identity-provider-facebook

その1点とは、そうですredirect_uriです。
直接フェデレーションの場合にFacebookに設定するredirect_uriはどうなるのか?というと、
https://login.microsoftonline.com/te/{テナントID}/oauth2/authresp
となります。

ここまで設定を進めると、ユーザーフローからAzure ADにアクセスするためのアプリケーション「aad-extensions-app」が自動的に登録されます。


ちなみに、このアプリケーションを削除すると面倒なことになるので、消さないようにしてください。
万一消してしまったらAzure AD B2Cでのb2c-extension-appと同じように回復をしてください。
参考)B2Cでの回復手順
https://docs.microsoft.com/ja-jp/azure/active-directory-b2c/extensions-app


ユーザーフローの登録

外部IDプロバイダの設定が終わったら、次はユーザーフローの登録です。
基本この辺りもAzure AD B2Cと同じです。


ユーザーフローの名称(ポリシー名となります)、利用するIDプロバイダ、連携する属性を設定していきます。
尚、属性は標準で用意されているもの以外に「カスタムのユーザー属性」メニューから自分で好きな属性を追加することもできます。

これでIDプロバイダとユーザーフローの登録はおしまいです。

アプリケーションの登録

次に、先ほど作成したユーザーフローを使ってサインアップやサインインを行う対象となるアプリケーションを登録します。
ポイントは、APIのアクセス許可で「User.Read」スコープに対して管理者による同意をしておくこと、です。現状のユーザーフローでのユーザ作成やログイン時にユーザ自身による同意が上手く取れない?のでこの設定は入れておく必要がありそうです。

ユーザーフローとアプリケーションの紐づけ

アプリケーションを作成したら、先に作成しておいたユーザーフローを使う様に構成をします。
先ほどのExternal Identitiesメニューに戻り、先ほど作成したユーザーフローを開きます。アプリケーションに関する設定項目がありますので、ここで作成したアプリケーションを指定します。

これで一通りの設定は終了です。

動作確認

では早速動かしてみます。

このアプリケーションにアクセスする為には、以下のパラメータをつけてAuthorizationエンドポイント(https://login.microsoftonline.com/{テナントID}/oauth2/authorize)へアクセスする必要があります。(通常のOpenID Connectのアプリケーションと同じです)

  • client_id={登録したアプリのClient Id}
  • response_type=id_token
  • scope=openid
  • nonce={生成したnonceの値。テストなら何でもOK}
  • redirect_uri={登録したアプリのredirect_url}

今回、アプリケーションとしてはhttps://jwt.msを使ったのでこんなURLになります。
https://login.microsoftonline.com/{テナントID}/oauth2/authorize?client_id={クライアントID}&response_type=id_token&scope=openid&nonce=hoge&redirect_uri=https:%2F%2Fjwt.ms

実行してみると、いつものサインイン画面になるので、まずはアカウントを作成します。

アカウントの作成画面に「Facebookアカウントでサインイン」というメニューが出来ています。
ここでFacebookアカウントでサインインすると、Azure ADへのアカウント登録を行う際に追加で登録するアカウントを入力する画面が出てきます。

続行するとアカウントがAzure AD上に登録されます。
アカウントタイプはゲスト、ソースはFacebookになっているのがわかります。


ちなみに登録後、サインインする場合はサインインオプションをクリックするとFacebookログインのメニューが出ていますので、そちらを使ってサインインします。


いかがでしょうか?
基本はAzure AD B2Cの機能の一部を通常のAzure ADの直接フェデレーション向けに開放しただけなので、Azure AD B2Cに慣れている方は直感的に理解できると思います。

しかし、冒頭にも書きましたが、こうなってくると「B2Bとは?」という疑問がやはり出てきますが大人しくしておきましょう。

おまけ

たまたまテストに使ったAzure ADにG-SuiteとのSSO設定があったので、SAMLの属性マッピングの調整をちょこっとして、GmailにAzure ADの直接フェデレーションを使ってFacebookアカウントでログインする、というくだらないものを作ってみたので動画を貼っておきます。

2020年5月16日土曜日

Azure ADを拡張してOpenID Connect for Identity Assuranceに対応させる

こんにちは、富士榮です。

先日、OpenID ConnectのID保証に関する拡張仕様である「OpenID Connect for Identity Assurance」について紹介しましたが、実際に仕様を理解するためには実装してみるのが一番、ということでAzure Active Directoryを拡張して実装してみようと思います。(尚、現状は完成している訳ではありません。軽く動作を確認してみた程度です)

Azure ADに足りないもの

OpenID Connect for Identity Assuranceを実装する上で、Azure ADに足りないものは最低限でも以下の通りです。

  • Authorizationエンドポイントがclaimsパラメータを受け付ける機能
  • verified_claims等の属性をid_tokenやuserInfoエンドポイントから返す機能

逆に、Azure ADでも以下について当然ですが出来ます。

  • 認証
  • ユーザデータを保持するデータベース
  • アプリケーション(Client)管理

実装に向けた戦略

ということで、以下の戦略で拡張してみることにしました。
  • Azure ADのエンドポイントをラップするWebアプリを用意し、request/responseの整形を行う
  • 認証、データ保持、アプリケーション管理はAzure ADの機能を使う
  • OpenID Connect for Identity Assuranceで拡張された属性(verified_claims関係)はAzure ADのスキーマを拡張する
こんな感じの仕組みになるはずです。


いざ実装

準備1:Azure ADのスキーマを拡張する

Azure ADにはスキーマ拡張を行う機能がありますので、今回はこの機能を使ってAzure AD上にID保証に関する情報、ID保証済みの属性を保存できるようにします。

Azure ADのスキーマ拡張に関するドキュメントはこちら

OpenID Connect for Identity Assuranceでは「verified_claims」というエレメントの定義がされており、配下に「verification」と「claims」というエレメントが存在します。
  • verification : IDの検証をどのように行ったのか?の記録
    • trust_framework : どんなルールによってIDの確認・検証をしたのか。例えば犯罪収益移転防止法や携帯電話不正利用法などの、本人確認を行った際の根拠法やルールを指定します
    • Evidence : ID確認時に利用した証拠(エビデンス)を指定します。免許証などですね。
      • type : エビデンスの種類を指定します。id_document(証明書類)、utility_bill(公共料金の領収書)、qes(欧州限定ですがeIDASの電子証明書)がありますが、ここではid_documentを使いました。
      • method : typeで指定したエビデンスをどのように確認したのか?を指定します。例えば対面で確認した場合は「Physical In-Person Proofing」ということで「pipp」という値を設定します。
      • document : typeにid_documentを指定した場合に、具体的に何の証明書類を使ったのか?を指定します。例えば日本の免許証なら「jp_drivers_license」を指定します。
      • issuer : 証明書を発行した機関に関する情報を指定します。
        • name : 機関名
        • country : 機関の属する国
      • date_of_issuance : 証明書類の発行日を指定します。
      • date_of_expiry : 証明書の有効期限を指定します。
  • claims : 検証された属性(ここは要求された属性を普通に指定します)
    • given_name : 検証済みの名
    • family_name : 検証済みの姓
    • など

この形でAzure ADの属性を拡張出来ればいいのですが、上記ドキュメントにもある通り、拡張できる属性の型はStringやInteger、DateTimeなどに限定され入れ子の構造や配列構造が取れません。本来なら複数のエビデンスを使ったID証明などもあり得ますが、現状はデータをフラットで持つしかありませんので諦めます。

具体的な方法は以下の通りです。
  • Directory.AccessAsUser.Allをスコープに指定してaccess_tokenを取得する
  • https://graph.microsoft.com/v1.0/schemaExtensionsに拡張したいスキーマの構造(JSON)をPOSTする
  • 成功したら他のクライアントからでも参照可能な様にスキーマをアクティベーションする(status属性をPATCHでActiveに更新する)
今回、以下の2つの拡張属性を作りました。
  • verification
  • verifiedClaims

それぞれのPOSTしたデータはこちらです。
- verification
{
    "id":"verification",
    "description": "verification extension for OpenID Connect for Identity Assurance",
    "targetTypes": [
        "User"
    ],
    "properties": [
        {
            "name": "verificationId",
            "type": "String"
        },
        {
            "name": "trustframework",
            "type": "String"
        },
        {
            "name": "evidenceType",
            "type": "String"
        },
        {
            "name": "evidenceMethod",
            "type": "String"
        },
        {
            "name": "evidenceDocumentType",
            "type": "String"
        },
        {
            "name": "evidenceDocumentIssuerName",
            "type": "String"
        },
        {
            "name": "evidenceDocumentIssuerCountry",
            "type": "String"
        },
        {
            "name": "evidenceNumber",
            "type": "String"
        },
        {
            "name": "evidenceDateOfIssurance",
            "type": "DateTime"
        },
        {
            "name": "evidenceDateOfExpiry",
            "type": "DateTime"
        }
    ]
}



- verifiedClaims
{
    "id": "verifiedClaims",
    "description": "verified claims extension for OpenID Connect for Identity Assurance",
    "targetTypes": [
        "User"
    ],
    "properties": [
        {
            "name": "verificationId",
            "type": "String"
        },
        {
            "name": "givenName",
            "type": "String"
        },
        {
            "name": "familyName",
            "type": "String"
        }
    ]
}


ちなみに、拡張スキーマの単位でid(属性名となります)をつけることが出来ますが、.net、.comなど限られたTLDでカスタムドメインをAzure ADに追加している場合は、[カスタムドメイン名]_[拡張属性名]という名前で属性を作ることが出来ますが、カスタムドメインを持っていない場合は「ext][8桁のランダム文字列]_[拡張属性名]という形で属性名が払い出されます。

当然ですが、この拡張属性に値を入れた状態のユーザを用意しておく必要があります。この辺りはGraph APIを使って値のセットをしてください。

準備2:Azure ADをラップするWebサービスを作る

ココが本番です。といってもやることはシンプルです。
(本来はちゃんと実装しないと危険ですので注意してください。本ポストにサンプルコードが出てきますが、かなり手抜きなのであくまで参考として捉えてください


  • Authorizationエンドポイント
    • clientからの要求の中でAzure ADが受け取ってくれないclaimsパラメータをパースして保持します。後からレスポンスを作るときに何が要求されていたのかを判断するために使います。
    • ※テスト実装では決めうち実装なので実際には保持していません
router.get('/authorize', (req, res) => {
    // get claims from request
    var _claims = req.query.claims;
    // redirect to Azure AD
    var target = lib.addQueryTo(conf.AAD_AuthZ, {
            response_type: 'code',
            scope: 'openid User.Read',
            client_id: req.query.client_id,
            state: req.query.state,
            redirect_uri: req.query.redirect_uri });
    res.redirect(target);
});


  • Tokenエンドポイント
    • codeを受け取ってAzure ADのTokenエンドポイントへ渡してaccess_token、id_tokenを受け取ります。
    • id_tokenには当然verified_claimsが含まれていないので、一度id_tokenをほどき、必要に応じてaccess_tokenを使ってMicrosoft Graphで追加の属性を取得、id_tokenを生成してclientへ返却します。
    • ※テスト実装ではPKCEに対応させていませんので、code横取りをして実装しています。本来はclientとPKCEに使う情報を共有しないと横取り出来ないので、ここはちゃんと考えないとダメです。
router.post('/token', async (req,res) => {
    try{
        let response_from_aad_token = await request({
            url: conf.AAD_Token,
            method: "POST",
            form: {
                grant_type: 'authorization_code',
                code: req.body.code,
                client_id: req.body.client_id,
                client_secret: req.body.client_secret,
                redirect_uri: req.body.redirect_uri
            },
            json: true
        })
        console.log(response_from_aad_token);
        // extract response and re-generate id_token with verified claims
        let decoded_jwt = jwt.decode(response_from_aad_token.id_token)

        // get additional claims using graph api
        let response_from_graph = await request({
            url: conf.AAD_Graph + "/v1.0/me?$select=id,userPrincipalName," + conf.AAD_ExtPrefix + "_verifiedClaims," + conf.AAD_ExtPrefix + "_verification",
            method: "GET",
            headers: {
                'Authorization': 'Bearer ' + response_from_aad_token.access_token,
                'Content-Type': 'application/json'
            }
        })
        console.log(response_from_graph);
        var user = JSON.parse(response_from_graph);
        // get properties from user object
        for (var prop in user){
            if(prop.includes('_verification')){
              var _verification = {
                trustframework: user[prop].trustframework,
                evidenceType: user[prop].evidenceType,
                evidenceMethod: user[prop].evidenceMethod,
                evidenceDocumentType: user[prop].evidenceDocumentType,
                evidenceDocumentIssuerName: user[prop].evidenceDocumentIssuerName,
                evidenceDocumentIssuerCountry: user[prop].evidenceDocumentIssuerCountry,
                evidenceDateOfIssurance: user[prop].evidenceDateOfIssurance,
                evidenceDateOfExpiry: user[prop].evidenceDateOfExpiry
              }
            } else if(prop.includes('_verifiedClaims')){
              var _verifiedClaims = {
                familyName: user[prop].familyName,
                givenName: user[prop].givenName
              }
            }
        }
        // create new jwt.
        var privateKey = fs.readFileSync('./private_key.pem', 'utf-8')
        var new_jwt = null;
        if (typeof _verification !== 'undefined') {
            new_jwt = jwt.sign({
                sub: decoded_jwt.sub,
                iss: 'https://' + req.headers.host,
                aud: decoded_jwt.aud,
                iat: decoded_jwt.iat,
                exp: decoded_jwt.exp,
                email: decoded_jwt.email,
                verified_claims: {
                    verification: {
                        trust_framework: _verification.trustframework,
                        evidence: [
                            {
                                type: _verification.evidenceType,
                                method: _verification.evidenceMethod,
                                document: {
 type: _verification.evidenceDocumentType,
 issuer: {
name: _verification.evidenceDocumentIssuerName,
country: _verification.evidenceDocumentIssuerCountry
 },
 number: _verification.evidenceNumber,
 date_of_issuance: _verification.evidenceDateOfIssurance,
 date_of_expiry: _verification.evidenceDateOfExpiry
                                }
                            }
                        ]
                    },
                    claims: {
                        given_name: _verifiedClaims.givenName,
                        first_name: _verifiedClaims.familyName
                    }
                }
            }, privateKey, { algorithm: 'RS256' });    
        } else {
            new_jwt = jwt.sign({
                sub: decoded_jwt.sub,
                iss: 'https://' + req.headers.host,
                aud: decoded_jwt.aud,
                iat: decoded_jwt.iat,
                exp: decoded_jwt.exp,
                email: decoded_jwt.email,
            }, privateKey, { algorithm: 'RS256' });    
        }
        
        res.send({
            token_type: "bearer",
            scope: "openid",
            expires_in: response_from_aad_token.expires_in,
            access_token: response_from_aad_token.access_token,
            id_token: new_jwt
        });
    } catch(e){
        console.log(e);
    }
});


  • userInfoエンドポイント
    • access_tokenを使ってMicrosoft Graphから必要な属性を取得して、整形した上でclientへ返却します
    • ※テスト実装ではここは実装していません。id_tokenにverified_claimsを入れて返却するところまでです。
  • その他
    • .well-known/openid-configurationとかjwks_uriは必要に応じて実装します。id_tokenの署名をAzure ADではなくこのWebサービス側で行うので対応した公開鍵などの情報をclientへ公開してあげる必要があります。

とりあえずこんな感じでnode.jsで実装してみています。


実際に動かす

テストするにしてもclientが必要になるので、phpでちょこっと書いておく必要があります。
やるべきことはclaimsパラメータを認証要求に乗せて検証済み属性を要求する、ということだけであとは普通のOpenID Connectのクライアントです。

こんな感じですね。
    // claims生成
    $verificationArray = array(
        'trust_framework'=>'null'
    );
    $claimsArray = array(
        'given_name'=>'null',
        'family_name'=>'null'
    );
    $verified_claimsArray = array(
        'verification'=>$verificationArray,
        'claims'=>$claimsArray
    );
    $id_tokenArray = array(
        'email'=>'null',
        'verified_claims'=>$verified_claimsArray
    );
    $claimsArray = array(
        'id_token'=>$id_tokenArray
    );
    
    // GETパラメータ関係
    $query = http_build_query(array(
        'client_id'=>$client_id,
        'response_type'=>$response_type,
        'redirect_uri'=> $redirect_uri,
        'scope'=>'openid User.Read',
        'state'=>$state,
        'nonce'=>$nonce,
        'claims'=>json_encode($claimsArray)
    ));
    // リクエスト
    header('Location: ' . $authorization_endpoint . '?' . $query );


動かすとこんな感じになります。
node.jsを動かすのにglitchを使っているので起き上がるまでにちょっと時間がかかってますが。。。


ということで、ここまでのソースはこちらにあります。
くれぐれも真似して使わない様にしてください。色々危ないので。
https://github.com/fujie/aad_oidc4ida

2020年5月2日土曜日

Azure ADの外部コラボレーションとBYOID

こんにちは、富士榮です。

リモートワークで組織外の方々とのコラボレーションが大きなテーマになっている方々も多いと思いますが、そういう時はAzure ADの外部連携(B2B)の機能ですよね。

そう言えばAzure ADのDirect FederationがGoogle以外にもSAML/ws-federationの外部IdPをサポートした、という会話をfacebookで某安納さんがしていたのを思い出したのと、先日 Alex Simons が色々と新しい機能が出たよ、というもはや個別の機能リリースの紹介だと無理だから一気にまとめて発表、みたいなポストをしていた中に、Azure AD B2CのSAMLサポートが正式リリースされた、という話があったので、もしやこれは繋がる???ということでやってみました。
いわゆるBYOD(Bring Your Own Identity)ってやつです。

当然、Azure ADのカスタムドメインを使った外部ID連携でも良いわけですが、この辺は後で比較をしてきます。


まず関連する記事、ドキュメント類を。


とりあえず完成イメージを

言葉で説明しても伝わりにくいと思うので、どういうことが出来るようになるのか?を動画にしています。
Azure AD B2Bで招待した外部ユーザでログインしようとすると、Azure AD B2Cにリダイレクトされ、LINEでログインすると、Azure ADにゲストユーザとして登録されてアプリケーションが使えるようになる、というシナリオです。


Azure AD B2CのSAMLサポート

先に書いた公式ドキュメント通りに設定していくと、Azure AD B2CがSAML IdPとして動作出来るようになります。(カスタムポリシーを使います。慣れると非常に簡単です)

大きな流れは以下の通りです。

Azure AD B2C側の設定
  • アサーション署名用の証明書(PFX)を作ってポリシーキーとしてアップロードする
  • ClaimsProviderとして、SAML2AssertionIssuerを作成する
    • MetadataにIssuerUriとして設定したものがIdPのEntityIDになります
    • CryptographicKeysに先にアップロードした証明書コンテナを指定するとアサーション署名をしてくれます(暗号化用証明書もアップロードすれば暗号化もできます)
こんな感じの設定になります。
<ClaimsProvider>
  <DisplayName>Token Issuer</DisplayName>
  <TechnicalProfiles>

    <!-- SAML Token Issuer technical profile -->
    <TechnicalProfile Id="Saml2AssertionIssuer_SAMPLE">
      <DisplayName>Token Issuer</DisplayName>
      <Protocol Name="SAML2"/>
      <OutputTokenFormat>SAML2</OutputTokenFormat>
      <Metadata>
        <Item Key="IssuerUri">https://nfpoc.b2clogin.com/nfpoc.onmicrosoft.com/B2C_1A_SI_SAML_SAMPLE</Item>
      </Metadata>
      <CryptographicKeys>
        <Key Id="MetadataSigning" StorageReferenceId="B2C_1A_samlsampleapp"/>
        <Key Id="SamlAssertionSigning" StorageReferenceId="B2C_1A_samlsampleapp"/>
        <Key Id="SamlMessageSigning" StorageReferenceId="B2C_1A_samlsampleapp"/>
      </CryptographicKeys>
      <InputClaims/>
      <OutputClaims/>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Saml-issuer"/>
    </TechnicalProfile>

    <!-- Session management technical profile for SAML based tokens -->
    <TechnicalProfile Id="SM-Saml-issuer">
      <DisplayName>Session Management Provider</DisplayName>
      <Protocol Name="Proprietary" Handler="Web.TPEngine.SSO.SamlSSOSessionProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
    </TechnicalProfile>
  </TechnicalProfiles>
</ClaimsProvider>


  • UserJourneyを定義する
    • この辺は通常のカスタムポリシーと同じです
  • RelyingPartyの定義を行う
    • 公式ドキュメントだとSAML SPに関するパラメータはAzure AD B2Cにアプリケーション定義を作ってマニフェストで設定する、ということになっていますが、Azure ADとB2Bとして連携する場合は、ちょっと工夫が必要です(後述します)

Azure AD側の設定

Azure ADの直接フェデレーションの設定を行います。
  • プロトコルはSAMLを選ぶ
  • IdPのドメイン名はSAML IdPの認証エンドポイントと同一ドメインである必要があるため、Azure AD B2Cのドメイン名(~.b2clogin.com)を指定する
  • 上記でAzure AD B2Cの設定が上手くいっていればSAML Metadataのダウンロードが出来るようになっていますので、保存したMetadataをアップロードして解析を行う
直接フェデレーションの設定画面。ここで「新しいSAML/WS-Fed IdP」を選びます。

こんな感じで設定します。



設定としては非常にシンプルです。
ただ、何点かクセがありますので、その部分を重点的に書いておきます。

設定のポイント

Azure AD B2Cのアプリケーション設定
  • ドキュメントを見ると、Azure AD B2Bと直接連携するためには以下の情報を設定する必要があることがわかります。
    • AssertionConsumerService
      • https://login.microsoftonline.com/login.srf
    • Audience(日本語ドキュメントだと「対象ユーザー」となっていますが。。。SAML SPのEntityIDのことです)
      • urn:federation:MicrosoftOnline
  • しかし、現状のAzure AD B2Cのアプリケーション登録ではカスタムスキームのAudience(urn:federation:MicrosoftOnline)をマニフェスト編集をしても登録することが出来ません。
  • ということで、SP(Azure AD)のMetadataを作って適当なところ(今回はbob)にアップロードし、カスタムポリシーから参照する形をとります。
こんな感じです。
<RelyingParty>
  <DefaultUserJourney ReferenceId="SI_SAML_SAMPLE" />
    <TechnicalProfile Id="PolicyProfile">
    <DisplayName>PolicyProfile</DisplayName>
    <Protocol Name="SAML2"/>
    <Metadata>
       <Item Key="PartnerEntity">https://nfpoccontent.blob.core.windows.net/root/aad_sp_meta.xml</Item>

手動で作ったSP Metadataはこんな感じです。単純にEntityIDとエンドポイントさえ書いてあれば最低限はOKです。

<?xml version="1.0"?>
<md:EntityDescriptor
    xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
    entityID="urn:federation:MicrosoftOnline"
    validUntil="2031-12-31T00:00:00.000Z">
  <md:SPSSODescriptor
    protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://login.microsoftonline.com/login.srf"/>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://login.microsoftonline.com/login.srf" index="0"/>
  </md:SPSSODescriptor>
</md:EntityDescriptor>

Azure AD B2CのSAML Metadata

  • 一応ドキュメントを漁ると出てくるのですが、見落としがちなので書いておきますが、以下のルールでMetadata URLが生成されます。
    • https://ドテナント名.b2clogin.com/テナント名.onmicrosoft.com/B2C_1A_ポリシー名/Samlp/metadata
Azure AD B2CからAzure ADへ渡す属性(SAML AssertionのAttributeStatement)

  • ドキュメントを見るとnameidがpersistentであること、emailaddressを属性として渡すこと、とありますので、それに合わせてAzure AD B2CのRelyingParty設定を行います。具体的にはOutputClaimsの設定です。
  • また、標準的なAzure AD B2CのSAML設定だと結構冗長な感じでAttributeStatementが書かれるので、TechnicalProfileのMetadataにSaml11AttributeEncodingInfo(Saml20~でもOK)を指定することで少しすっきりします。
こんな感じのRelyingParty定義になります。

<RelyingParty>
  <DefaultUserJourney ReferenceId="SI_SAML_SAMPLE" />
  <TechnicalProfile Id="PolicyProfile">
    <DisplayName>PolicyProfile</DisplayName>
      <Protocol Name="SAML2"/>
      <Metadata>
        <Item Key="PartnerEntity">https://nfpoccontent.blob.core.windows.net/root/aad_sp_meta.xml</Item>
        <Item Key="Saml11AttributeEncodingInfo">
          <![CDATA[
            <saml:AttributeStatement xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
            <saml:Attribute AttributeName="emailaddress" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
            <saml:AttributeValue>
            </saml:AttributeValue>
            </saml:AttributeStatement>]]></Item>
      </Metadata>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="objectId"/>
        <OutputClaim ClaimTypeReferenceId="b2cmail" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" />
      </OutputClaims>
      <SubjectNamingInfo ClaimType="objectId" ExcludeAsClaim="true"/>
    </TechnicalProfile>
  </RelyingParty>
</TrustFrameworkPolicy>

ちなみにClaimType「b2cmail」は適当に適宜した属性なので自由に定義してもらえれば良いかと思います。重要なのは、「Azure AD B2Cのドメインと同じドメイン名を持つメールアドレスが設定されていること」です。例えば、hoge.b2clogin.comというAzure AD B2Cドメインを使っている場合は、fuga@hoge.b2clogin.comという値を返す必要があります。
※この辺りは早くAzure AD B2Cがカスタムドメインをサポートしてくれないと実利用するには辛いですね。

いざ、動作確認

設定はこれでおしまいです。
しかし、あくまでこれはAzure AD B2Bにおける「外部ユーザの招待において独自のパスワードを払い出さずに済ませるための機能(これ重要)」なので、あらかじめユーザを招待しておく必要があります。

ここは通常の招待と同じなので詳細は割愛しますが、Azure AD B2Cのドメインと同じドメインのユーザを指定して招待する、というところがポイントです。
つまり、メールは届きません。(少なくともb2clogin.comを使っている限り、一般人はこのドメインでメールは届かないと思います)

ただ、招待されている状態であれば招待元のアプリケーションにアクセスすれば招待の承認と同じことが起きますので、運用でカバーです。



そして、これも公式ドキュメントに記載されていますが、現状B2Bの直接フェデレーションはマルチテナントアプリケーションでのホームレルムディスカバリが使えないので、招待元ディレクトリのテナントIDやドメイン名が明示的に指定されているアプリケーションしか動きません。
例えば、

  • https://myapps.microsoft.comはダメ
  • https://myapps.microsoft.com/?tenantid=xxxxxxxxはOK
という感じです。


ここまで行けば、冒頭に動画で紹介した動きが再現できるはずです。
ちなみに動画内では条件付きアクセスを使ってゲストユーザの初回ログイン時に利用規約に同意させる様にしています。

カスタムドメイン単位でのフェデレーションと何が違うのか?

ここで疑問が出てくるのが、元々Azure ADにはカスタムドメイン単位で外部のSAML/WS-FederationのIdPと連携する機能があります。ちょっと前に多くの企業がAD FSをオンプレにおいてOffice365とSSOをやっていた構成ですね。

もちろんこのケースは社内ユーザを想定した話ですが、技術的に言うとB2Bの直接フェデレーションとほぼ変わりません。

ただ、細かく見るとちょっとずつ違います。
非常に雑な比較ですが、こんな感じです。

カスタムドメインのID連携B2Bの直接フェデレーション
フェデレーション単位ドメイン単位ドメイン単位
ユーザの管理あらかじめ作成が必要(Azure AD Connectでの同期など)
ImmutableIdでのマッチング
あらかじめ招待が必要
メールアドレスでのマッチング
アプリケーションの制限マルチテナントでも可
また、WS-Fedでwindowstranportエンドポイントの整備などの条件を満たせばWindows 10 PCログオンも可(Webサインイン設定不要)
マルチテナントアプリは不可
当然Windows 10 PCログインには使えない




ということで、実用的かどうかはユースケース次第というところですが、日々色々な機能が拡張されてきているな、というところです。



2020年4月29日水曜日

eKYCとOpenID Connectに関する最近のアクティビティ

こんにちは、富士榮です。

年明けから異常な忙しさでほぼ4か月ぶりの投稿になってしまいました。

今日はOpenID Foundationが今年から本格的に仕様化を進めているeKYC、ID保証に関する新しい仕様である「OpenID Connect for Identity Assurance」について紹介したいと思います。
ワーキンググループが始まったころは、ここまでリモートワークや非対面が重要になる、という局面を誰も予測していませんでしたが、ここに来てオンラインでのID保証、eKYCに関するニーズが本格化しているのは恐ろしい偶然としか言いようがありません。

尚、この「OpenID Connect for Identity Assurance」は、ちょうど1年程前から私もOpenIDファウンデーションジャパンのKYCワーキンググループをリードさせていただいている関係で共同議長を務めさせていただいている、米国OpenID FoundationのeKYC and Identity Assurance Working Groupで仕様策定が進められています。

ID保証(Identity Assurance)とは?

アイデンティティ管理に必ず付いて回るのが、デジタル・アイデンティティの精度や鮮度をはじめとする「信頼性」の課題です。

例えば、エンタープライズのシナリオでは、そのアカウントの持ち主はまだ在籍しているのか、権限付与に使っている役職は正確なのか?などといった「アイデンティティ・ライフサイクル」を管理するためにMicrosoft Identity ManagerやLDAP Managerなどの製品を使って効率的かつ正確なアイデンティティ管理を行います。

また、コンシューマのシナリオではいわゆる「KYC:Know Your Customer」といわれるID保証に関する課題を常に抱えています。これは例えば、サービスの利用を開始する際に登録する氏名や住所、年齢などの属性が本当に正しいのか?という「本人確認」や「身元確認」と言われる課題で、サービスの種類によっては利用者が一定の年齢を超えていることが法令で決められていたり、サービスの利用を許可するために必要な収入や本人到達性などの要件を満たす必要があったりするケースにおいては非常に重要なID保証のユースケースの一つです。例えば、金融機関におけるAML(Anti Money Laundering:マネーロンダリング防止)やオンラインゲーム等における年齢制限などがユースケースとして該当しますね。


ちなみにIDに関する保証レベルを綺麗に体系化しているのが「NIST(米国国立標準技術研究所)」が発行しているセキュリティに関するガイドラインシリーズ(SP/Special Publications)の800-63です。※通称NIST SP800-63
尚、NIST SP800-63は連邦政府内の情報保護の要件を規定している「NIST SP800-53」の中のデジタル・アイデンティティに関するガイドラインとして位置付けられていますが、連邦政府と取引をする民間企業を対象としてNIST SP800-53から抜き出す形で定義された「NIST SP800-171」にも同様に適用されることなどから、実質的にデジタル・アイデンティティの保証に関するデファクトスタンダードとなっています。

脱線を続けますが、NIST SP800-63では以下の3つの区分でID保証に関する規定をしています。

  • Identity Assurance Level(IAL):NIST SP800-63A
    • ID登録を行う時の本人確認・身元確認の強度(自己申告<非対面<対面など)
  • Authenticator Assurance Level(AAL):NIST SP800-63B
    • 認証器の強度(単要素<ソフトウェアベースの多要素<ハードウェアベース多要素など)
  • Federation Assurance Level(FAL):NIST SP800-63C
    • アサーション保護の強度(Bearer+署名<Bearer+署名+暗号化<HoK+署名+暗号化など)

各ガイドラインの日本語訳をOpenIDファウンデーション・ジャパンが公開しているので、興味のある方は参考にしてください。
https://openid-foundation-japan.github.io/800-63-3-final/index.ja.html


このように単にアイデンティティに関する「保証」といっても多岐にわたって考慮すべき点があります。


OpenIDファウンデーションジャパンにおける活動

冒頭で述べた通り、OpenIDファウンデーションジャパンにおいても先に述べた「KYC」に関するワーキンググループを2019年1月より設置し活動をしています。

このワーキンググループではOpenID Connectの仕様の拡張などといった単純に技術にフォーカスしたワーキングではなく、日本国内の各種業界(金融、テレコム、古物など)におけるID保証、本人確認に関する現状の調査、および現在業界毎に分断されている本人確認に関する要件の共通化に向けた課題の洗い出しなどポリシー面からの整理や、OpenID Connect for Identity Assuranceの仕様への国内事情のインプット、分散台帳を活用したアイデンティティの保証として注目されている「DID:Decentralized Identifier」や「VC:Verifiable Credentials」といった新しい技術の調査も、NFCリーダを使った公的証明書のICチップ読み取りや画像認識などeKYCに関連する技術に関しても網羅的に調査を行っています。

現在、ワーキンググループは第1シーズンの活動を終え、第2シーズンの活動を開始したところですので、興味のある方はこの機会にOpenIDファウンデーションジャパンへの入会とワーキンググループへの参画をご検討ください。

尚、第1シーズンの活動実績と成果物については2020年1月に開催された「OpenID Summit Tokyo」の中で発表させていただきましたので、詳しくは発表資料・成果物をご覧ください。



米OpenID Foundationにおける活動

ようやくOpenID Connectの仕様の話です。米OpenID Foundationでは2020年1月より「eKYC and Identity Assurance Working Group」という新しいワーキンググループを立ち上げ、ID保証に関するOpenID Connectの新しい仕様である「OpenID Connect for Identity Assurance(通称OIDC4IDA)」の策定を進めています。尚、先に述べた通り、OpenIDファウンデーションジャパンでKYCワーキンググループをリードさせていただいている縁もあり、こちらのワーキンググループでは議長であるDr. Torste Lodderstedtの元、Mark Haine, Anthony Nadalinと共に共同議長を務めさせていただいております。

どのような仕様か

Abstractに記載されているとおり、本仕様は「OpenID Providerがエンドユーザに関する検証済みの属性をRelying Partyに対して提供するためのOpenID Connectの拡張」であり、「特定の法律に基づき自然人のアイデンティティを検証するために利用されることを意図して」定義されています。
This specification defines an extension of OpenID Connect for providing Relying Parties with verified Claims about End-Users. This extension is intended to be used to verify the identity of a natural person in compliance with a certain law.

簡単に言うと、OpenID Providerに登録されているアイデンティティが何に基づき、どのように検証されたものか?というメタデータを含めてRelying Partyに対して提供することにより、アプリケーション側が安心してサービスを提供することが出来るようにすることを目指しています。

例えば、ial_exmample_goldというトラストフレームワークに則って確認済みのgiven_nameとfamily_name属性の値である、ということをRelying Partyに伝達する時、id_tokenやuserInfoエンドポイントから以下のような形でverified_claimsとしてJSONが返却されます。
{
   "verified_claims":{
      "verification":{
         "trust_framework":"ial_example_gold"
      },
      "claims":{
         "given_name":"Max",
         "family_name":"Meier"
      }
   }
}

細かい技術面での解説はAuthleteの川崎さんが解説してくれていますし、少し前のDraftですが仕様の日本語化もされていますので、そちらをご覧いただければと思います。



仕様の現状

現在、本仕様は2nd Implementer's draftのレビュー期間が終わり、仕様の承認に関する投票が開始されています。
https://openid.net/2020/04/27/notice-of-vote-for-second-implementers-draft-of-openid-connect-for-identity-assurance-specification/

こちらも興味があれば米国OpenID Foundationの会員(個人でもなれます)として加入していただき、仕様策定にコントリビュートしていただければと思います。
(意外と日本人もいますよ!)




2019年12月31日火曜日

年忘れはFIDOで!指紋認証付きのeWBM Goldengateを試す

こんにちは、富士榮です。

世の中は大晦日ですが、今日になってeWBMさんよりGoldengate G310が届いたので軽く試してみます。

届いたのは、G310というFIDO2対応のキーです。
https://www.ewbm.com/security-keys/goldengate-g310/



韓国から直送で送られてきたっぽいです。
まだ日本のAmazonでは買えないので、直販サイトか米国Amazonで買うのがいいみたいです。
 直販サイト
  https://www.ewbm.com/store/products/goldengate-g310/
 米国Amazon
  https://www.amazon.com/s?k=ewbm+g310&ref=nb_sb_noss_2

定価は60ドルですが、直販サイトだと初回は45ドルで買えるみたいです。
(私はMVPのサードパーティ特典で頂きました)


なんだかキーばっかり増えちゃって微妙な感じですが、Yubikeyとの大きな違いは指紋認証機能があることですね。
キーだらけ・・・


やれることは正直他のFIDO2対応のキーと変わらないので、今更動きについて解説する必要もないと思いますので、先日のAXIES(大学ICT推進協議会)の年次大会でお見せしたG SuiteへAzure AD B2C+FIDO実装を使ってパスワードレスでログインするデモを張っておきます。



最大の特徴の指紋登録ですが、以下の2通りの方法で設定できます。
1.公式のツール「Goldengate BioManager」を使う
2.Windowsのアカウント設定を使う

それぞれ簡単に。
まずは公式ツール「Goldengate BioManager」を使う方式から。

ツールはこの辺りからダウンロードできます。
 https://www.ewbm.com/support/BioManager/

ちなみにダウンロードしてセットアップしようとするとWindowsに実行をブロックされますが無視して進めます・・・

セットアップが終わりツールを起動するとこんな画面が出てきます。
言語の切り替えもできて、日本語も選べます。


次は指紋の登録です。

指紋追加を選ぶとタッチする様に促されるのでベタベタ触ります。


登録が終わると、登録済みの指紋以外でタッチしてもキーが反応しなくなります。
あと、PINが聞かれなくなります。
※指紋登録していない状態だと単純にタッチ+PINだけでサインインできる(Yubikeyと同じ挙動)が、指紋登録をすると登録していない指だとサインインできない。


ちなみに、2つ目の登録手段であるWindowsの標準の設定です。
設定⇒アカウントのセットアップから登録できます。

ちなみにWindows Helloの設定も同じ設定メニュー内にありますが、あくまでセキュリティキーの設定なのでここでセットアップしてもセキュリティキーでWindowsログインが出来るようになるわけではありません。もしOSログインにも使いたければ使うアカウントの設定をMicrosoftアカウントなりAzure ADアカウントなりで設定する必要があります。

指紋の追加は先ほどのBioManagerとほぼ同じです。




セキュリティキー+PINだけでは不安な方は指紋認証機能も付いているGoldengateも一つの選択肢となりえますね!

2019年11月30日土曜日

Hyperledger Indy, Ursa, Ariesのオンラインコースで自己主権型アイデンティティについて学習する

こんにちは、富士榮です。

先日開催されたW3C/TPAC@福岡でDID WGが正式に発足したり、DIFやSovrin Foundationが活発に活動していたり、と自己主権型アイデンティティ(Self Sovereign Identity/SSI)や分散型アイデンティティ(Decentralized Identifier/DID)のムーブメントも本格化してきましたね。

そういえば@IdentityWomanことKaliya姉さんがおそらく世界初のSSIの本「A Comprehensive Guide to Self Sovereign Identity」を出版したのも今年の4月だったのでまだ半年ちょっとしか経ってないんですよね。


そんな中、元々Evernym~Sovrin Foundationが開発してきたSSIのための分散台帳技術でLinux Foundationの進めるHyperledgerプロジェクトの一員となったHyperledger Indyや、Wallet間のPeer to peer通信のプロトコルの標準化を進めようとしているHyperledger Aries、暗号化ライブラリであるHyperledger UrsaのオンラインのトレーニングコースがedXで公開されました。
※日本語記事あり:https://crypto.watch.impress.co.jp/docs/news/1220/815/index.html

HyperledgerファミリーとIndy, Ursa, Aries(出典:Evernym)

ざっくりいうと、Indyが分散台帳技術そのものであるのに対して、UrsaはZKP(ゼロ知識証明)などに使う暗号化技術、AriesはWalletやAgent間の通信に関する技術、という整理っぽいですね。(私もまだ勉強中なので間違っていたらすみません)

IndyとAriesの関係の整理(出典:Evernym)



ということで、もう少し深く勉強してみるいい機会なのでコースを受講してみようと思います。(せっかくなので受講証明がもらえる有料コース/99USドルにしました。12/2までならクーポンコード「CYBER20」を入れると20ドルくらい割引になります)

以下、コース登録までの道のりです。(実際の内容はこれからなので、また気づきがあれば書こうと思います)

1.edXのコースサイトを開く

こちらが今回のターゲットとなるコースのURLです。
https://www.edx.org/course/identity-in-hyperledger-aries-indy-and-ursa


ここからEnrollを押してスタートしていきます。

2.会員登録を行う

メールアドレスで登録するか、Facebook、Google、Microsoftアカウントでアカウントを作成します。


作成が終わると、有料会員に誘われますw。せっかくなのでお金を払ってみようと思います。

3.有料会員登録を行う

会員登録後、誘われるがままに登録をしてみます。

どうやら有料登録をすると色々と特典があるようです。(最後のは特典なのか?)

  • Unlimited Course Access
  • Graded Assignments
  • Easily Sharable
  • Support our Mission
「Pursue the Verified Track」で迷わず購入へ進めましょう。

左の中央に「Add coupon code」があるので先ほどの「CYBER20」を登録すると20ドルくらい安くなります。(12/2までらしいですが)
クレジットカードがPayPalで支払えますが、私はPayPalで支払いました。

購入が終わると登録者の本人確認が行われます。

4.本人確認を行う(eKYC!!)

方法としてはWebカメラで顔とIDドキュメント(パスポートや免許証など。名前と顔写真が一致していることを見るっぽいので、実質日本人で使えるのはパスポートだけだと思いますが)の写真を撮り1日~2日くらい審査があるようです。


まずは顔写真を撮ります。(黒線は筆者都合w)

次にパスポートの写真を撮ります。

両方揃えてアップロードして審査待ちです。



5.コースの受講を開始する

とりあえず登録はこれで終わりなので、ゆっくりコースを受講していきます。


コース目次は以下の通りです。ゆっくり学習していこうかと。

  • Welcome!
  • Chapter 1: Something Is Missing
  • Chapter 2: Adding a Layer of Trust to the Internet
  • Chapter 3: SSI Using Indy, Aries and Ursa
  • Chapter 4: A Blockchain for Identity
  • Chapter 5: The All-Important Agent, Or Rather, Agents!
  • Chapter 6: When Things Go Wrong
  • Chapter 7: Possibilities
  • Final Exam

なかなか興味深いです。





2019年11月29日金曜日

Ignite Tour TokyoでAzure AD B2C + SSI/DIDの話をします

こんにちは、富士榮です。

あっという間に11月も終わりですね・・・

なんだか最近、月に2~3本の勢いでイベントでお話をしている気がします。おかげで色々と実験はしているもののBlogへのアウトプットが後手後手に回ってしまっていて反省です。

まぁ、その分講演の中では実験した内容を含めじっくりお話させていただいているつもりなので、是非機会があれば見に来ていただければ、ということで。

ということで、あっという間に来週の開催になってしまいましたが、12/5~6にIgnite Tour Tokyoがあり、こちらでAzure AD B2C + SSI/DIDの話をすることになっています。
基本的には前のde:codeEuropean Identity & Cloud ConferenceConsumer Identity World USAでお話してきた内容の続きではありますが、この自己主権型アイデンティティや分権型IDってキーワードが全くもってキャッチーじゃないので、地道に世界観を浸透させていくしかないのかな?と思っています。

ということでIgnite Tour Tokyoでは「分権型IDテクノロジーによる効率的な外部ユーザのID管理」というタイトルで「B2BやB2Cのシナリオにおいて外部アイデンティティの管理、特にIDの確認(KYCなど)は管理者にとって非常に頭の痛い問題です。本セッションではそれらの問題をAzure AD B2Cとブロックチェーンを使った自己主権型アイデンティティのシナリオでどのように解決するのかについて説明します。」という話をする予定です。

セッションコード:BRK30053
セッションURLはこちら)
https://tokyo.myignitetour.techcommunity.microsoft.com/sessions/87724


絶賛、過去のスライドをかき集めて準備中ですw

ベースはこの辺りなので、事前に目を通しておいていただけると理解がはやいかも。
de:codeの資料
 https://www.slideshare.net/naohiro.fujie/kyc-identity-on-blockchain
OSSコンソーシアムの資料
 https://www.slideshare.net/naohiro.fujie/kyc-153297605
didconの資料
 https://www.slideshare.net/naohiro.fujie/ssidid


では、当日お会いしましょう。