目次


MEAN をマスターする

OAuth と Passport による認証の管理

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: MEAN をマスターする

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:MEAN をマスターする

このシリーズの続きに乞うご期待。

UGLI (User Group List and Information) アプリケーションは申し分のない形になってきました。現時点では、作成したローカルのコンテンツを、「MEAN をマスターする: MEAN とレスポンシブ Web デザインを適用した UGLI CRUD」でセットアップした CRUD 画面で表示できるようになっています。さらに、「MEAN をマスターする: MEAN と Meetup.com、Microdata との融合」で開発したサービスを利用して、外部サイトのコンテンツを組み込むこともできます。

このプロジェクトの重要な部分は、ミーティング情報を一般の人々と共有することですが、私はユーザー会のリーダーとして、一部のアクティブティーについては、ユーザー会に登録されているメンバーだけにアクセスを制限することも考えています。例えば、匿名アクセスを無効にしてログインを要求することで、プレゼンテーションに関してコメントする際の礼儀が守られるようにしたいと考えています。したがって、今回の記事では Meetup.com の OAuth サービスを利用して、UGLI アプリケーションにログイン機能を追加します。サンプル・コードを入手するには、「ダウンロード」を参照してください。

新規ユーザー・アカウントの作成

アプリケーションのユーザーは「Signup (サインアップ)」ボタン (図 1 を参照) をクリックして、新規アカウントを作成できるようになっています。作成されたアカウントは、ローカルで MongoDB に保存されます。この機能はアプリケーションに組み込まれているため、追加でプログラミングする必要はありません。

図 1. UGLI サインアップ・ページ
UGLI サインアップ・ページのスクリーンショット
UGLI サインアップ・ページのスクリーンショット

このデフォルトの動作は、開発の観点からすると最も簡単なソリューションであることは確かですが、ユーザー・エクスペリエンスの観点では十分ではありません。ユーザー会のメンバーはすでに Meetup.com のアカウントを持っていて、その既存のアカウントを使って、今後のミーティングについての出欠確認 (RSVP (repondez s'il vous plait) 処理) を行っています。これらのユーザーに重複する資格情報一式を作成して保持するよう求めるのは、迷惑なだけでなく、DRY (Don't Repeat Yourself) 原則に明らかに違反しています。

幸い、現在使用している MEAN スタックでは、OAuth と Passport を利用する分散型の認証および認可ソリューションをセットアップすることができます。簡単に言うと、ユーザーは、Meetup.com にログインする際に使用するのと同じ資格情報を使用して UGLI アプリケーションへのログイン (認証) を行えるということです。ただしこれは、ユーザーの許可がないと行うことができません。つまり、ユーザーは自分の Meetup.com 資格情報を UGLI アプリケーションが使用することを許可 (認可) しなければなりません。

OAuth によって UGLI アプリケーションが認可されたとしても、認可されたアプリケーションとユーザー資格情報が共有されることはありません。ローカルでユーザー名とパスワードを重複して UGLI に保管するという方法は取らないからです。認可されることを求めるアプリケーション (UGLI) は、ユーザーを OAuth プロバイダー (Meetup.com) にリダイレクトします。ユーザーはそこで、自分の資格情報 (ユーザー名とパスワード) を入力します。ユーザーの認証が成功すると、認可されたアプリケーションにアクセス・トークンが返されるという仕組みです。

この仕組みは、アプリケーションのコードとロジックを大幅に削減します。例えば、暗号化されたパスワードをサーバー上に保管する方法について、もはや懸念する必要はありません。それは、OAuth プロバイダーが対処すべき問題です。同様に、強力なパスワードを強制するためのアルゴリズムを作成する必要も、パスワードを忘れた場合に対処する必要も、あるいはユーザーに定期的にパスワードを変更させる必要もなくなります。

このように、OAuth はユーザーが記録しなければならないパスワードの数を減らすと同時に、開発者が作成しなければならないコードを大幅に減らしてくれます。もしこれが、教科書に定義されている Win-Win のシナリオには当てはまらないというのであれば、私には Win-Win というものがわかりません。

OAuth と Passport の概要

強力なパスワードを強制するためのアルゴリズムを作成する必要も、パスワードを忘れた場合に対処する必要も、あるいはユーザーに定期的にパスワードを変更させる必要もなくなります。

OAuth は分散型の認証と認可を実装するためのオープン・スタンダードで、2006年に Twitter およびビジネス・パートナーの Ma.gnolia により、認可されたサービスからの情報を表示するデスクトップ・ウィジェットを容易に作成できるようにするために開発されました。それ以来、OAuth は Google から Facebook、Twitter、GitHub、LinkedIn、等々に至るまで、何百にも上る主要な Web サイトで採用されています (有名な OAuth サービス・プロバイダーのリストを参照してください)。

Passport は Node.js 用に作成された OAuth ライブラリーです。厳密に言えば、これは Express アプリケーションにシームレスに組み込まれるように意図されたミドルウェアです。さまざまな OAuth プロバイダーごとにカスタマイズされた 140 を超える Passport プラグイン (「ストラテジー (strategy)」と呼ばれます) が利用できるようになっています。

UGLI アプリケーションの package.json ファイルをテキスト・エディターで開くと (リスト 1 を参照)、4 つの主要なサービス (Facebook、Twitter、LinkedIn、Google) の Passport ストラテジーと、資格情報を MongoDB に直接保管するためのローカル・ストラテジーが使用されていることがわかります。

リスト 1. package.json 内の Passport ストラテジー
"dependencies": {
    "passport": "~0.2.0",
    "passport-local": "~1.0.0",
    "passport-facebook": "~1.0.2",
    "passport-twitter": "~1.0.2",
    "passport-linkedin": "~0.1.3",
    "passport-google-oauth": "~0.1.5"
}

Meetup.com を UGLI の OAuth プロバイダーとして利用するために、これから既存のサンプル・コードを参考に、6 つ目のストラテジーを追加します。

Meetup.com の Passport ストラテジーをインストールする

Authenticating with the Meetup API」にアクセスすると、Meetup.com では OAuth サービスを提供していることがわかります。Web で「meetup.com passport.js strategy」をさっと検索してみると、探している passport-meetup ライブラリーへのリンクが見つかります。

npm install passport-meetup –save」と入力して、このライブラリーを node_modules にダウンロードし、package.json で依存関係のブロックを更新します。

以上のように、ストラテジーをインストールするのは今回の作業の簡単な部分です。ストラテジーを入手した後は、次のステップとして、このストラテジーをサインアップ・ページとサインイン・ページに組み込みます。

Meetup.com リンクをサインアップ・ページとサインイン・ページに追加する

テキスト・エディターで ppublic/modules/users/views/signup.client.view.html を開きます。ファイルの先頭には、さまざまな OAuth プロバイダーへのリンクが組み込まれています (リスト 2 を参照)。

リスト 2. public/modules/users/views/signup.client.view.html
<h3 class="col-md-12 text-center">Sign up using your social accounts</h3>
<div class="col-md-12 text-center">
    <a href="/auth/facebook" class="undecorated-link">
        <img src="/modules/users/img/buttons/facebook.png">
    </a>
    <a href="/auth/twitter" class="undecorated-link">
        <img src="/modules/users/img/buttons/twitter.png">
    </a>
    <a href="/auth/google" class="undecorated-link">
        <img src="/modules/users/img/buttons/google.png">
    </a>
    <a href="/auth/linkedin" class="undecorated-link">
        <img src="/modules/users/img/buttons/linkedin.png">
    </a>
</div>

既存のリンクを、これから作成する /auth/meetup ルートを指すリンクで置き換えます (リスト 3 を参照)。このリンクは、この後ダウンロードする Meetup.com アイコンを表示します。

リスト 3. auth/meetup へのリンク
<h3 class="col-md-12 text-center">Sign up using your Meetup.com account</h3>
<div class="col-md-12 text-center">
    <a href="/auth/meetup" class="undecorated-link">
        <img src="/modules/users/img/buttons/meetup.png">
    </a>
</div>

Meetup Icon」ページにアクセスして、128x128 ピクセルの画像を public/modules/users/img/buttons/ に保存します。このディレクトリーには、他のソーシャル・メディアのアイコンも格納されています。

サインアップ・ページのスタブが作成されたので、次はテキスト・エディターで public/modules/users/views/signin.client.view.html を開き、サインアップ・ページに対して行ったのと同じ要領でこのファイルに手を加えます (リスト 4 を参照)。

リスト 4. public/modules/users/views/signin.client.view.html
<h3 class="col-md-12 text-center">Sign in using your Meetup.com account</h3>
<div class="col-md-12 text-center">
    <a href="/auth/meetup" class="undecorated-link">
        <img src="/modules/users/img/buttons/meetup.png">
    </a>
    </div>

すべてが計画通りに進んでいれば、新しいサインアップ・ページは図 2 のように表示されます。もちろんルートはまだ設定されていないので、リンクをクリックすると 404 Page Not Found エラーとなります。このエラーについては、次のセクションで対処します。

図 2. 新しい UGLI サインアップ・ページ
新しい UGLI サインアップ・ページのスクリーンショット
新しい UGLI サインアップ・ページのスクリーンショット

サーバー・サイドの auth/meetup ルートをセットアップする

次のステップでは、サーバー・サイドの auth/meetup ルートを作成します。サーバー・サイドのロジックはすべての app ディレクトリーに保管され、クライアント・サイドのロジックは public フォルダーに保管されていることを思い出してください。

テキスト・エディターで app/routes/users.server.routes.js を開きます。Facebook のコード・ブロックを見つけてコピーし、コピーしたブロックに含まれる「facebook」を「meetup」に置き換えてペーストします (リスト 5 を参照)。

リスト 5. app/routes/users.server.routes.js
// Setting the facebook oauth routes
app.route('/auth/facebook').get(passport.authenticate('facebook', {
    scope: ['email']
}));
app.route('/auth/facebook/callback').get(users.oauthCallback('facebook'));

// Setting the meetup oauth routes
app.route('/auth/meetup').get(passport.authenticate('meetup', {
    scope: ['email']
}));
app.route('/auth/meetup/callback').get(users.oauthCallback('meetup'));

前のセクションでサインアップ・ページおよびサインイン・ページ上に作成した auth/meetup へのハイパーリンクを覚えていますか?最初のルート (auth/meetup) は、ユーザーがリンクをクリックして HTTP GET リクエストをサーバーに送信するとトリガーされます。すると、Passport が passport-meetup ストラテジーを使用してユーザーの認証を試行します。ログイン試行の結果 (成功またはそれ以外) は 2 番目の auth/meetup/callback ルートに非同期で送信されます。

サインアップ・ページで Meetup へのリンクをクリックすると、今度は 404 ではなく 500 Server Error となります。必ずしも改善されているとは言えませんが、少なくとも進歩はしています。そこで次は、Meetup ストラテジーを構成します。

Meetup ストラテジーを構成する

Passport ストラテジーはすべて、その名を冠した config/strategies ディレクトリーにあります。facebook.js を meetup.js にコピーした後、テキスト・エディターで meetup.js を開いてください。

前のセクションで行ったのと同じように、このファイル全体で「facebook」と記述されているすべての箇所を「meetup」で置き換えます。ただし、今回は単純な検索/置換の操作では終わりません。簡単な構成の変更も行う必要があります。

まず始めに、ファイルの先頭にある必須ライブラリーを Facebook ストラテジーから Meetup ストラテジーに変更します (リスト 6 を参照)。

リスト 6. config/strategies/meetup.js
/**
 * Module dependencies.
 */
var passport = require('passport'),
    url = require('url'),
    MeetupStrategy = require('passport-meetup').Strategy,
    config = require('../config'),
    users = require('../../app/controllers/users');

続いて、新しいストラテジーに渡されるオプション・ブロックをカスタマイズする必要があります。これらのオプションの値は、ストラテジーごとに異なります。リスト 7 に、Facebook ストラテジーのオプションを記載します。これらのオプションは、Meetup には機能しません。

リスト 7. Meetupには機能しない Facebook ストラテジーのオプション
module.exports = function() {
    // Use facebook strategy
    passport.use(new FacebookStrategy({
            clientID: config.facebook.clientID,
            clientSecret: config.facebook.clientSecret,
            callbackURL: config.facebook.callbackURL,
            passReqToCallback: true
        },

ありがたいことに、前に npm install によってインストールした passport-meetup モジュールは、サンプル・コードに同梱されています。node_modules/passport-meetup/examples/login/app.js をテキスト・エディターで開いてください。このファイル内で、passport.use の関数呼び出しを見つけます (リスト 8 を参照)。

リスト 8. node_modules/passport-meetup/examples/login/app.js
passport.use(new MeetupStrategy({
    consumerKey: MEETUP_KEY,
    consumerSecret: MEETUP_SECRET,
    callbackURL: "http://127.0.0.1:3000/auth/meetup/callback"
  },

上記のスニペットを meetup.js にコピーして、Facebook のコードを上書きします。次に、コロンの右側にある値を、リスト 9 に記載する値に変更します。

リスト 9. Meetup で機能するオプション
passport.use(new MeetupStrategy({
    consumerKey: config.meetup.consumerKey,
    consumerSecret: config.meetup.consumerSecret,
    callbackURL: config.meetup.callbackURL,
    },

次のセクションでは、Meetup.com から consumerKeyconsumerSecret を受け取って、これらの情報を config.js ファイルに保存します。ただしその前に、現在のファイルにいくつかの変更を加える必要があります。

new MeetupStrategy コンストラクターのすぐ後に続く関数は、Meetup.com からのレスポンスを受け取るイベント・ハンドラーです。ここで関心の対象となるのは、イベント・ハンドラーが受け取るレスポンスに含まれる要素のうち、アクセス・トークン、リフレッシュ・トークン、ユーザー・プロファイルの 3 つです (このうち、2 つのトークンの詳細については、囲み記事「OAuth のアクセス・トークンとリフレッシュ・トークン」を参照してください)。

アクセス・トークンとリフレッシュ・トークンは、変更せずにそのまま Passport に渡すストリングです。OAuth 操作を成功させるには、これらのトークンが不可欠ですが、それ自体は何の変哲もないストリングです (リスト 10 に、両方のトークンの例を記載します)。

ユーザー・プロファイルには、トークンよりも興味深い点があります。ユーザー・プロファイルは OAuth プロバイダーから JSON オブジェクトとして返され、そこには認証に成功したユーザーに関する情報が格納されています。具体的な詳細は OAuth プロバイダーによって異なります。リスト 10 に、Meetup から返されるユーザー・プロファイルの例を記載します。

リスト 10. Meetup.com OAuth プロバイダーから返されたユーザー・プロファイル
{ 
  provider: 'meetup',
  id: 13848777,
  displayName: 'Scott Davis',
  _raw: '{ 
      "results": [{
        "status": "active",
        "link": "http:\\\/\\\/www.meetup.com\\\/members\\\/13848777",
        "photo": {
          "photo_link": "http:\\\/\\\/photos1.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\
\/member_11849906.jpeg",
          "thumb_link": "http:\\\/\\\/photos3.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\
\/thumb_11849906.jpeg",
          "photo_id": 11849906
        },
        "country": "us",
        "state": "CO",        
        "city": "Denver",
        "id": 13848777,
        "joined": 1295844957000,
        "bio": "Scott Davis is the founder of ThirstyHead.com, a training and 
consulting company that specializes in leading-edge technology solutions like 
HTML 5, NoSQL, Groovy, and Grails.",
        "name": "Scott Davis",
        "other_services": {
          "twitter": {
            "identifier": "@scottdavis99"
          }
        }
      }]  
   }',
  _json: { 
     results: [ [Object] ],
     meta: { 
        link: 'https://api.meetup.com/2/members',
        total_count: 1,
        url: 'https://api.meetup.com/2/members?order=name&member_id=13848777&offset=0
&format=json&page=800',
        title: 'Meetup Members v2',
        updated: 1392763702000,
        description: 'API method for accessing members of Meetup Groups',
        method: 'Members',
     },
     accessToken: 'c7b5577bb80aab55439785cd86abcdef',
     refreshToken: '2af98db68950235a1e2519a734abcdef' 
  } 
}

ご覧のように、Meetup から返されるユーザーの詳細には、名前、住所、登録日、プロフィールの写真、ソーシャル・メディア・アカウントへのリンク、等々があります。

Meetup ストラテジーのカスタマイズを完了するために必要な最後の作業は、Meetup から返されるプロファイルのフィールドを、app/models/user.server.model.js に定義されている User という Mongoose オブジェクトにマッピングすることです。それには、config/strategies/meetup.js の残りの Facebook ブロックをリスト 11 に示すように編集します。

リスト 11. OAuth ユーザー・プロファイルと User オブジェクトのマッピング
// Create the user OAuth profile
var providerUserProfile = {
    firstName: '',
    lastName: '',
    displayName: profile.displayName,
    email: '',
    username: profile.id,
    provider: profile.provider,
    providerIdentifierField: 'id',
    providerData: providerData
};

Meetup から返される JSON フォーマットのプロファイルを調べて、User オブジェクトに追加したいフィールドが見つかったら、この機会に変更を加えてください。新しいフィールドは、忘れずに public/modules/users/views の HTML フォームに追加する必要があります。

最終的な config/strategies/meetup.js はリスト 12 のようになっているはずです。

リスト 12. 完成した config/strategies/meetup.js
'use strict';

/**
 * Module dependencies.
 */
var passport = require('passport'),
    url = require('url'),
    MeetupStrategy = require('passport-meetup').Strategy,
    config = require('../config'),
    users = require('../../app/controllers/users');

module.exports = function() {
    // Use meetup strategy
    passport.use(new MeetupStrategy({
        consumerKey: config.meetup.clientID,
        consumerSecret: config.meetup.clientSecret,
        callbackURL: config.meetup.callbackURL,
        },
        function(req, accessToken, refreshToken, profile, done) {
            // Set the provider data and include tokens

            var providerData = profile._json;
            providerData.accessToken = accessToken;
            providerData.refreshToken = refreshToken;

            // Create the user OAuth profile
            var providerUserProfile = {
                firstName: '',
                lastName: '',
                displayName: profile.displayName,
                email: '',
                username: profile.id,
                provider: profile.provider,
                providerIdentifierField: 'id',
                providerData: providerData
            };
            
            // Save the user OAuth profile
            users.saveOAuthUserProfile(req, providerUserProfile, done);
        }
    ));
};

このコードをテストする前に必要な作業が 1 つあります。それは、Meetup から consumerKeyconsumerSecret を入手することです。

Meetup から consumerKeyconsumerSecret を入手する

この記事のここまでは、ユーザーの認証について取り上げてきましたが、ユーザーが OAuth を使用して UGLI にログインするには、開発者があらかじめ、開発者の組織がその自称する組織であることを証明しなければなりません。そのためには、公開鍵 (consumerKey) をユーザーに提供します。また、アプリケーションがその公開鍵に対応する秘密鍵 (consumerSecret) を把握する必要もあります。

PKI (Public Key Infrastructure) を扱った経験があれば、秘密鍵を隠して保護することが非常に重要であることをご存知のはずです。他の誰かが秘密鍵を見つけた場合、その組織になりすます可能性があります。その一方で、開発者が公開鍵をユーザーと共有しなければ、ユーザーは開発者の身元を証明することができません。

Meetup.com のユーザー会の事務局メンバーになっていれば、Meetup の「OAuth Consumers (OAuth コンシューマー)」ページから consumerKeyconsumerSecret を生成することができます。私は、「Consumer name (コンシューマー名)」として「HTML5 Denver User Group」を、「Application Website (アプリケーション Web サイト)」として「http://www.meetup.com/HTML5-Denver-Users-Group/」を、「Redirect URI (リダイレクト URI)」として「http://localhost:3000/auth/meetup/callback」を指定しましたが、UGLI アプリケーションを本番環境にプッシュした後、「Application Website (アプリケーション Web サイト)」を「http://html5denver.com」に、「Redirect URI (リダイレクト URI)」を「http://html5denver.com/auth/meetup/callback」に変更する予定です。

図 3. HTML5 Denver 用の consumerKeyconsumerSecret の生成
図 3. HTML5 Denver 用の consumerKey と consumerSecret を生成する画面のスクリーンショット
図 3. HTML5 Denver 用の consumerKey と consumerSecret を生成する画面のスクリーンショット

Meetup.com のユーザー会の事務局メンバーでない場合は、そのアカウントでユーザー会の代表として OAuth 鍵を生成することはできません。ただし、他のいずれかのソーシャル・メディア・アカウントのトークンを生成して、この記事で説明したステップをそのソーシャル・メディアに合わせて調整することは可能です。Twitter アカウント用のアプリケーション・キーを生成するには「Implementing Sign in with Twitter」を、Facebook アカウントを使用するには「Access Tokens」を参照してください。Web でさっと、「your social media website oauth keys」を検索すると、ステップ・バイ・ステップの説明が見つかるはずです。

組織の OAuth 鍵をアプリケーションに追加する

2 つの鍵 (公開鍵と秘密鍵) を取得した後は、環境変数を使って、それらの鍵をアプリケーションに追加します。その方法は、「MEAN をマスターする: MEAN アプリケーションについて探る」で PORT を変更した方法と同様です。

実行中のモードに応じて (developmentproduction、または test に) 変更される変数を設定できることを思い出してください。環境固有の値は config/env に保管されます。テキスト・エディターで config/env/development.js を開き、Facebook ブロックをコピー・アンド・ペーストして Meetup に合わせて調整します (リスト 13 を参照)。属性名は、config/strategies/meetup.js で passport.use 関数呼び出しに使用した属性名と一致するようにしてください。

リスト 13. config/env/development.js
'use strict';

module.exports = {
    db: 'mongodb://localhost/test-dev',
    app: {
        title: 'HTML5 Denver'
    },

    meetup: {
        consumerKey: process.env.MEETUP_KEY || 'APP_ID',
        consumerSecret: process.env.MEETUP_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/meetup/callback'
    },    
    facebook: {
        clientID: process.env.FACEBOOK_ID || 'APP_ID',
        clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/facebook/callback'
    },
    twitter: {
        clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
        clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
        callbackURL: 'http://localhost:3000/auth/twitter/callback'
    },
    google: {
        clientID: process.env.GOOGLE_ID || 'APP_ID',
        clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/google/callback'
    },
    linkedin: {
        clientID: process.env.LINKEDIN_ID || 'APP_ID',
        clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/linkedin/callback'
    }
};

APP_IDAPP_SECRET は、前のセクションで取得した consumerKeyconsumerSecret の値をハードコーディングして置き換えることもできますが、これらの値は、環境変数によって UGLI アプリケーションに提供したほうが、よりセキュアなソリューションになります。組織の consumerKeyconsumerSecret を使用してアプリケーションを起動するには、以下のコマンドを入力します。

MEETUP_KEY=l75fkklhurkack36eelfhhfhjc MEETUP_SECRET=abcdeg316jd3ni43f21u1abcde NODE_ENV=development grunt

アプリケーションを本番に移す前に、必ず config/env/production.js に対しても同様の調整を行ってください。前にユーザー・アカウントを作成した場合は、そのアカウントを MongoDB の html5-denver-dev データベースから削除することもお忘れなく。そうすることで、新しいアカウントの作成プロセスをもう一度最初から辿ることができます。

まとめ

「Easy things should be easy, and hard things should be possible」という、Larry Wall (Perl プログラミング言語の作成者) の有名な言葉どおり、簡単なことは簡単にするべきであり、難しいことは可能にするべきです。この言葉が、今回の記事で分散型認証および認可のニーズに対応するために、OAuth と Passport を構成して Meetup.com を使用するようにした皆さんの経験を見事に言い表していることを願います。

「MEAN をマスターする」の次回の記事では、MEAN スタックに組み込まれたテスティング・インフラストラクチャーを詳しく取り上げます。サーバー・サイドをテストするための Mocha、クライアント・サイドをテストするための Jasmine、そして複数のブラウザーでテストを実行するための Karma について学んでください。それまでは、楽しみながら MEAN をマスターしてください。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source
ArticleID=1005475
ArticleTitle=MEAN をマスターする: OAuth と Passport による認証の管理
publish-date=05142015