Azure Active Directory の SSO 開発 (Node.js 編)


New Azure Portal を使用した SAML の設定 (Node.js プログラミング含む) については、「Azure AD – How to register your own SAML-based application using new Azure Portal」(英語) を参照してください。

環境 :
Node.js v0.10.21
Express 3.4.8
Passport 0.2.0
passport-azure-ad 0.0.2

開発者にとっての Microsoft Azure Active Directory

こんにちは。

今回は、Node.js を使った Azure AD (Windows Azure Active Directory) とのフェデレーション (SSO) について、手順を紹介しておきます。
なお、ここでは SAML-P を使用しますが、OAuth 2.0 / OpenID Connect による Azure AD との連携サンプルは、「Office 365 API – PHP, Node.js, etc での使用」を参考にしてください。

本サンプルでは、Node.js でアイデンティティ連携をおこなう際にはよく使われている (らしい) Passport を使用します。Passport を使用する際には、Facebook 用の Passport のパッケージ、Twitter 用の Passport のパッケージなど、公開されている専用のパッケージを組み合わせて使用しますが、MS Open Tech (Microsoft Open Technologies, Inc.) では、Passport を使った Azure AD 用の passport-azure-ad パッケージを提供しており、これをインストールすることで難解な設定をおこなわずに Azure AD とのフェデレーションが可能です。
もちろん、すべてオープンソースとして提供されています。

[Microsoft Interop] Node.js Library for Authentication with Windows Azure Active Directory

http://blogs.msdn.com/b/interoperability/archive/2013/05/22/node-js-library-for-authentication-with-windows-azure-active-directory.aspx

[Github] passport-azure-ad

https://github.com/MSOpenTech/passport-azure-ad

 

Express の設定

ここで使用する Node.js の Passport は、Express のアプリケーション フレームワークが前提となっています。
このため、まず、Express をインストールしておきましょう。

npm install express

Express では、フレームワークを使用するための面倒な設定をおこなう必要はなく、下記の通り express コマンドを使って Web アプリケーションのひな形を作成できます。
今回は「sample01」というサブ フォルダーを作成して、ここに Web アプリケーションを作成します。

mkdir sample01
express -e sample01

つぎに、npm コマンドを使って、関連する残りのパッケージをすべてインストールします。(sample01package.json に書かれている依存パッケージがすべてインストールされます。)

cd sample01
npm install

この Web アプリケーションの start point は、app.js (sample01app.js) です。上記で自動生成された app.js には、例えば、以下の通り記述されているはずです。

var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/users', user.list);

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

この app.js では、必要なライブラリのロード (require) や、Express フレームワークを使った必要な設定 (app.set, app.use) がおこなわれています。まずは、Express が作成したこの Web アプリケーションがちゃんと動作することを確認してみてください。
node app と入力して app.js を実行するか、IIS (または Windows Azure Websites) を使用する場合は「App Service Editor で Node.js 開発」で解説したように、以下のような Web.config を構成します。

<configuration>
  <system.webServer>
    <handlers>
      <add name="iisnode" path="sample01/app.js" verb="*" modules="iisnode" />
    </handlers>
    <rewrite>
      <rules>
        <rule name="app">
          <conditions>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True" />
          </conditions>
          <action type="Rewrite" url="sample01/app.js" />
        </rule>
      </rules>
    </rewrite>
    <!-- node_modules をちゃんと隠す場合 -->
    <!--
    <security>
      <requestFiltering>
        <hiddenSegments>
          <add segment="node_modules" />
        </hiddenSegments>
      </requestFiltering>
    </security>    
    -->
  </system.webServer>
</configuration>

サイトにアクセスすると、以下のような画面が表示されるはずです。

ここでは、Express のフレームワークの解説は省略しますが、コントローラー (Route) や画面 (View) の追加などをおこなって動作を確認してみてください。

 

Windows Azure Active Directory の設定

では、ここから Identity Federation のためのセットアップをおこないます。
まず準備として、Windows Azure Management Portal を使って Windows Azure Active Directory にアプリケーションを登録しておきましょう。

Windows Azure Management Portal で Azure AD のテナントをクリックして、[アプリケーション] (Applications) タブを選択して [追加] ボタンを押し、表示される画面で [組織で開発中のアプリケーションを追加] (Add an application my organization is developing) を選択します。
以降、追加するアプリケーションの情報を設定するウィザードが表示されるので必要な情報を設定すれば完了です。(アプリケーションの URL などを設定します。)

アプリケーションの作成 (登録) が完了したら、[エンドポイントの表示] をクリックして、Federation Metadata の URL をコピーしておいてください。(この Federation Metadata の中に、証明書などのフェデレーションに必要な情報が設定されています。)

 

Passport のインストール

つぎに、Node.js のアプリケーション (上述の Express アプリケーション) に、Passport と、Windows Azure Active Directory 用の Passport パッケージ (MS Open Tech 作成のパッケージ) をインストールします。
以下のコマンド (npm) を実行してインストールします。

npm install passport
npm install passport-azure-ad

ここで、飲み会でも話しましたが、オープンソースらしい重要な設定がありますので注意してください。(このおかげで、登壇直前の木曜は会社でハマってました。やはり、セミナー準備を直前にやるのは危険ですね。。。)
実は、2014 年 03 月現在、この passport-azure-ad にバグがあるようで、コミュニティにより、この修正のための Pull リクエスト (修正依頼) が発行されています。現在、この修正を反映しないと「Invalid Signature」の Error が発生するので注意してください。(いずれパッケージ本体に正式に反映されるでしょう。)
このリクエストは以下に記載されていますので、以下を確認してソースコードを修正してください。

https://github.com/MSOpenTech/passport-azure-ad/pull/7/files#r8684656

このバグ (不具合) は、Windows Azure Active Directory が渡す証明書のうち最初の証明書しか確認していないというもので、上記では、渡されたすべての証明書を確認するようソースコードを修正しています。

 

Passport を使用した Windows Azure Active Directory の認証

必要なパッケージなどの準備は整いましたので、以降では Passport を使用した Windows Azure Active Directory とのフェデレーションをプログラミングします。

Passport を使用したプログラミングの流れは、Passport のライブラリーや Strategy をロードし、Express に Passport を使用するためのセットアップをおこない、Passport を使用した処理本体 (後述する passport.authenticate、passport.serializeUser/deserializeUser など) を記述します。(あとは、アプリケーションの動作に応じてに必要な機能を、随時、追加・変更します。)
この基本的な手順は、Facebook を使う場合も、Twitter を使う場合も、そして Windows Azure Active Directory を使用する場合も同様です。

まず、上記の Express が作成した Web アプリケーションの app.js に、以下の通りコードを追記します。
ここでは、Passport のライブラリーをロードし、Windows Azure Active Directory 用の Strategy を作成しています。なお、Windows Azure Active Directory の Passport パッケージ (passport-azure-ad) では、WS-Federation と SAML-P の双方をサポートしていますが、今回は SAML-P (SamlStrategy) を使用しています。

var passport = require('passport');
var SamlStrategy = require('passport-azure-ad').SamlStrategy;

Passport では、アイデンティティ・プロバイダー (Facebook, Twitter, Azure AD 等) による認証が完了すると、cookie を使用してユーザー情報など (セッション) を維持します。Express でこうした振る舞いを可能にするため、app.js に下記太字の通りコードを追記します。
なお、設定する場所や順番なども注意してください。

. . .
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());

// Added for passport
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());

app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
. . .

つぎに、Passport に、Windows Azure Active Directory 用の Strategy を登録します。

このための準備として、あらかじめ、OpenSSL などを使用して証明書を作成し、この Web アプリケーションに配置しておきます。今回は、公開キーを持つ証明書を public.pem、秘密キーを持つ証明書を private.pem と仮定します。

証明書の準備が出来たら、下記のコードを追記します。
下記で、identityMetadata には上記で (Azure AD から) コピーした Federation Metadata の Url を設定し、privateCert、publicCert には上記の証明書を設定します。(issuer、appUrl は、この Web アプリケーションの URL です。)

var fs = require('fs');
. . .

passport.use(new SamlStrategy({
    identityMetadata: 'https://login.microsoftonline.com/6cf16624-15f0-4157-8cde-963a80d854bd/federationmetadata/2007-06/federationmetadata.xml',
    loginCallback: 'https://nodetest01.azurewebsites.net/login/callback/',
    logoutCallback: 'https://nodetest01.azurewebsites.net/logout/callback/',
    issuer: 'https://nodetest01.azurewebsites.net',
    appUrl: 'https://nodetest01.azurewebsites.net',
    privateCert: fs.readFileSync('./private.pem', 'utf-8'),
    publicCert: fs.readFileSync('./public.pem', 'utf-8')
  }, function(profile, done) {
    // when authenticated, simply hold profile in session
    process.nextTick(function () {
      done(null, profile);
    });
  }));
. . .

上記により、Windows Azure Active Directory のログイン完了後に、上記の loginCallback に指定された URL (/login/callback) が呼び出されます。
このため、Express フレームワークを使用して、下記の通り app.js に /login/callback の処理を記述します。ここでは、passport.authenticate によって、渡された SAML の Validation をおこないます。(下記の通り failureFlash を true に設定することで、エラー メッセージをユーザーに表示します。)

app.post('/login/callback',
  passport.authenticate('saml', { failureRedirect: '/', failureFlash: true }),
  function(req, res) {
    res.redirect('/');
  }
);

Validation が完了すると、Passport は取得したユーザー情報 (クレーム情報など) をパースして、この Express アプリケーションに設定します。(この際、内部で Cookie などを使用します。)
この処理をおこなうために、以下の通り記述します。

passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(id, done) {
  done(null, id);
});

さいごに、Windows Azure Active Directory を使用した認証処理を呼び出します。
今回は、デモで紹介したように、このサイトの /login を呼び出すと、Windows Azure Active Directory に認証要求するように構築してみましょう。Express フレームワークを使用して、app.js に以下の通り処理を記述します。

app.get('/login',
  passport.authenticate('saml'),
  function(req, res) {
    res.redirect('/');
  });

以上で簡単なフェデレーションの処理が完了しました!
最終的な app.js は、以下のようになっているはずです。(太字が追加部分です。)

var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');
var fs = require('fs');
var passport = require('passport');
var SamlStrategy = require('passport-azure-ad').SamlStrategy;

var app = express();

passport.use(new SamlStrategy({
    identityMetadata: 'https://login.microsoftonline.com/6cf16624-15f0-4157-8cde-963a80d854bd/federationmetadata/2007-06/federationmetadata.xml',
    loginCallback: 'https://nodetest01.azurewebsites.net/login/callback/',
    logoutCallback: 'https://nodetest01.azurewebsites.net/logout/callback/',
    issuer: 'https://nodetest01.azurewebsites.net',
    appUrl: 'https://nodetest01.azurewebsites.net',
    privateCert: fs.readFileSync('./private.pem', 'utf-8'),
    publicCert: fs.readFileSync('./public.pem', 'utf-8')
  }, function(profile, done) {
    // when authenticated, simply hold profile in session
    process.nextTick(function () {
      done(null, profile);
    });
  }));

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/users', user.list); // This time, we don't use this...
app.get('/login',
  passport.authenticate('saml'),
  function(req, res) {
    res.redirect('/');
  });
app.post('/login/callback',
  passport.authenticate('saml', { failureRedirect: '/', failureFlash: true }),
  function(req, res) {
    res.redirect('/');
  }
);

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

passport.serializeUser(function(user, done) {
  done(null, user);
});
passport.deserializeUser(function(id, done) {
  done(null, id);
});

試しに、動作確認をしてみましょう。
Web アプリケーションを起動して /login のアドレスにアクセスしてみてください。下図の通り、Windows Azure Active Directory の SignIn 画面にリダイレクトされるはずです。

 

Claim の取得

上述の通り、認証に成功すると、取得した情報がパースされて Web アプリケーションに設定されます。(実体は、Cookie が使用されます。)
この情報は、Express が各ページの処理で毎回渡す req (Request Object) に入っており、req.user として、Claim などの詳細情報を含んだ User オブジェクトを取得できます。(試しに、JSON.stringify などで req.user をパースしてみてください。)

例えば、Web アプリケーションのHome ページ (root) で、ログインしているユーザーの情報を表示してみましょう。
今回は Express フレームワークの詳細の解説は省略しますが、ざっくり書くと、Express では、各ページの処理は routes フォルダーにライブラリーとして格納されており、対応する View は views フォルダーに入っています。(routes の処理から View が呼び出されます。初期設定では、View エンジンとして Ejs を使用します。)
Express が作成した上記の Web アプリケーション (app.js) の設定の場合、/ (Home) は、routes/index.js がライブラリー (コントローラー) 本体で、views/index.ejs が View になります。

このため、今回は、routes/index.js を開いて、以下の通り記述してみます。
以下は、受け取った req (Request Object) からログイン ユーザーの givenName と email の情報を取り出して View に渡す簡単なサンプルです。

exports.index = function(req, res){
  if(req.user)
    res.render('index', { username: req.user.givenName, mail: req.user.email });
  else
    res.render('index', { username: null});
};

views/index.ejs では、上記で渡された username, mail を使って、以下の通り記述します。

<!DOCTYPE html>
<html>
  <head>
    <title>Passport Test</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <% if (!username) { %>
    <h2>Not logged in...</h2>
    <% } else { %>
    <h2>Hello, <%= username %>.</h2>
    (your e-mail is <%=mail %>)
    <% } %>
  </body>
</html>

この Web アプリケーションの /login にアクセスして Windows Azure Active Directory で認証をおこなうと、認証後は、/login/callback で検証がおこなわれ、/ (Home) にリダイレクトされて、最終的に、下図のように、ログインしたユーザー情報やメールアドレスが表示されます。

 

MS Open Tech が提供している Github のサンプル コードでは、こうした処理の他に、logout の処理や、node-waad を使用した Graph API へのアクセスなど、さらに応用的なコードも含まれていますので、是非参考にしてみてください。

Comments (0)

Skip to main content