IBM Cloud App ID Node.js Webクイックスタート

#はじめに
IBM Cloud App IDは、ユーザー認証を手軽に組み込めるサービスです。
AWSでいうとCognitoに相当するサービスです。

今回は、IBM Cloudのドキュメントで公開されている[Node.js Webクイックスタート](https://cloud.ibm.com/docs/appid?topic=appid-web-node&locale=ja)を補足＆若干の変更を加えてながらやってみたいと思います。

なお、本記事で紹介するソースコードは、[ここ](https://github.com/yo24/APPID-Quitck-Start)からダウンロード可能です

#環境

ローカルPC上のNode.js(v10.6.3)を利用します。
Node.js自体のセットアップ手順は割愛します。

#クイックスタートでやること

クイックスタートでは、OAuth 2.0 [認可コードフロー](https://openid-foundation-japan.github.io/rfc6749.ja.html#grant-code)を用いた認証するアプリケーションを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/3d39f9af-952f-fc03-384a-4769a2fa3ab6.png)

#画面遷移

具体的な手順の前に、クイックスタートで作成する画面遷移を紹介します。

###初期表示
index.htmlの初期表示は以下の通りです。 Loginリンクがあるだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/aa15fad5-a9d2-1ed6-8ae9-e83bcb06dad0.png)

###ログイン画面 by App ID
Loginをクリックすると、App IDが表示するログインフォームへ遷移します(画面はカスタマイズしてませんが)。
フロー図でいうと(3)に相当します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/b04f6971-57ad-3fe5-d696-53f1b0649f4d.png)

###ログイン後
ログイン後に表示するindex.htmlは以下の通りです。
ログインユーザーの情報を出力し、かつリンクがLogoutに変更されています。
フロー図でいうと(8)に相当します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/90371885-6809-80a5-2b17-91477179279e.png)

###パスワード忘れ時の救済画面、ユーザー登録画面
なお、ログイン画面で、Forgot password? および Sign upをクリックすると、それぞれ以下の画面へ遷移します。

Forgot password
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/7f3ae100-cc17-d4ab-381c-bf6a2635508c.png)
Sign up
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/b9d82e47-a1ff-5c1a-e595-18964f3f9196.png)

#App IDの設定

まず、App IDの設定を行います。

IBM Cloudへログイン後、CatalogからApp IDを検索します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/7f2dc35b-4f4d-247f-445f-a4070c0bd0eb.png)

次画面で、RegionおよびPlanを選択し、Createします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/43065ed6-66e0-c979-23a6-8fed841c0cc6.png)

サービス作成後、左メニューのManage Authenticationを選択し、Authentication Settingsタブを表示させます。
Add web redirect URLsに、認可コード取得後のリダイレクトURLを入力します。前述のフロー図では(5)のリダイレクト先になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/7449069e-ab85-e2a2-def0-4920959d2abf.png)

サンプルユーザーを登録します。
左メニューのCloud Directory -> Userを選択し、次にCreate Userをクリックします
以下の画面が表示されるので、必要な情報を入力し、Saveします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/688f60bc-b56b-3257-a9c9-6973656d283f.png)

Save後、ユーザ一覧が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/72fd10f9-0d03-23c0-1545-9f928adff429.png)

最後に、Applicationを登録します。
Applicationは、OAuthクライアントを表す概念で、フロー図ではアプリ・サーバーが相当します。
左メニューのApplicationを選択し、Add Applicationをクリックし、必要情報を入力します。
今回はサーバー側で画面制御をするので、Typeは「Regular web application」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/ab924cab-3118-adc4-11f3-ac33ce3ce01a.png)

登録後、一覧が表示されます。
以降のステップで、詳細情報が必要になるので確認しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/1bb982f5-dd79-d470-d4e9-dd059fe44ab6.png)


#Node.jsの設定

package.jsonの作成および、アプリに必要なpackageをインストールします。

まずはpackageから。
App IDのNode.js SDKを利用します。
このSDKは、[Passport](http://www.passportjs.org/)のStrategyとして機能します。
また、Passportはログイン結果をセッションに保存するので、セッション管理の仕組みとしてexpress-sessionも必要になります。

```console
npm install --save express express-session passport log4js pug
npm install --save ibmcloud-appid
```
package.jsonも作成しましょう。
mainで起動するファイル名は、```app.js```とします。

```console
npm init

==省略==

package name: (node_appid) node-appid
version: (1.0.0)
description:
entry point: (index.js) app.js
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /work/node_appid/package.json:

{
  "name": "node-appid",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "dependencies": {
    "express": "^4.17.1",
    "express-session": "^1.17.0",
    "ibmcloud-appid": "^6.0.2",
    "log4js": "^6.1.0",
    "passport": "^0.4.0",
    "pug": "^2.0.4"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
Is this OK? (yes) yes
```

#app.jsの作成

パーツごとに解説を交えながら、コーディング内容を記載します。

###初期化

まずは、初期化部分です。
ここで、WebAppStrategyの初期化で設定している各種値は、App IDでApplication登録時に取得した値および、Authentication Settingsで登録したRedirect URLになります。
また、express-sessionのデフォルトストアはメモリーなので、このままではスケールアウトはできないことに注意してください。スケールさせるためには、Redis等の外部サービスを保管先に指定する必要があります(本記事では触れません)。

```javascript:app.js(初期化)
const express = require('express');                                 // https://www.npmjs.com/package/express
const log4js = require('log4js');                                   // https://www.npmjs.com/package/log4js
const session = require('express-session');                            // https://www.npmjs.com/package/express-session
const passport = require('passport');                                // https://www.npmjs.com/package/passport
const WebAppStrategy = require('ibmcloud-appid').WebAppStrategy;    // https://www.npmjs.com/package/ibmcloud-appid

// logger
var logger = log4js.getLogger('testApp');

// setup express-session. default store is memory
const app = express(); app.use(session({
  secret: 'hogehoge',
  resave: false,
  saveUninitialized: false
}));

// setup passport
app.use(passport.initialize()); // initialization is mandatory
app.use(passport.session());  // use session
passport.serializeUser((user, cb) => cb(null, user));
passport.deserializeUser((user, cb) => cb(null, user));
passport.use(new WebAppStrategy({ // WebAppStragegy is passport Strategy
  tenantId: "xxxxxxxx-xxxx-4460-xxxx-750f7a624e01",
  clientId: "xxxxxxxx-xxxx-4c42-xxxx-bb4765e130e7",
  secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  oauthServerUrl: "https://jp-tok.appid.cloud.ibm.com/oauth/v4/xxxxxxxxxx",
  redirectUri: "http://localhost:3000/appid/callback"
}));

```

###ログイン

認証を行うには、passport.authenticateを利用します。
passportは、具体的な認証手段の実装であるStrategyをauthenticateの第１引数に設定します。
ここで設定するのが、App IDのSDKの1機能であるWebAppStrategyです。

WebAppStrategyのパラメーターですが、Login成功時のRedirect先は、index.htmlへ戻したいので```/```を設定しています。
また、ログイン有無に関係なく、ログインフォームを表示させたいので```forceLogin: true```を設定します。

この処理は、フロー図の(2)と(8)に相当します。

```javascript:app.js(login)
// Login
app.get('/appid/login',
  passport.authenticate(WebAppStrategy.STRATEGY_NAME, {
    successRedirect: '/',
    forceLogin: true
}));
```

###Callback

基本的には、```passport.authenticate```を実行するだけです。
認可コードをインプットに、アクセストークンを取得してくれます。
フロー図で言うと、(5)から始まり(6)、(7)に相当します。

処理の前半は、認可コードをログ出力しているだけで、必須ではありません。

```javascript:app.js(callback)
// Callback from appid
app.get('/appid/callback',
  (req, res, next) => {
    logger.info('call back is called. authorized Code =' + req.query.code);
    next();
  },
  passport.authenticate(WebAppStrategy.STRATEGY_NAME)
);
```

###ログアウト

これも簡単で、```WebAppStrategy.logout```を実行するだけです。

```javascript:app.js(logout)
// Logout
app.get('/appid/logout', (req, res) =>{
  WebAppStrategy.logout(req);
  res.redirect('/');
});

```

###ユーザー情報取得

この処理は、ログイン後に表示するユーザー情報をjsonで応答します。
ログインユーザーの情報は、req.userで取得可能です
未ログインの場合、同オブジェクトは取得できないので、HTTP Status Code 401 Unauthorizedを返しています。 

```javascript:app.js(get_user)
// get user info api
app.get("/api/user",
  (req, res) => {
    if (!req.user) {
      res.status(401);
      res.send('');
    } else {
      logger.info(req);
      res.json({
        user: {
          name: req.user.name,
          email: req.user.email,
          given_name: req.user.given_name,
          family_name: req.user.family_name
        }
      });
    }
  }
);
```

###その他

最後に、index.htmlを保存するディレクトリを登録し、port:3000でリクエストを待ち受けます。

```javascript:app.js(etc)

// Server static resources
app.use(express.static('./public'));

// Start server
app.listen(3000, () => {
  console.log('Listening on http://localhost:3000');
});

```

#index.html

最後に、index.htmlです。
非常に簡単な内容なので、解説は省略します。

```html:index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Simple Node Web App index</title>
</head>

<body>
  <h1>Hello from the app!</h1>
  <a href="/appid/login" id="login">Login</a>
  <a href="/appid/logout" id="logout" style="display: none;">Logout</a>
  <h3 id="name"></h3>
  <h3 id="email"></h3>
  <h3 id="given_name"></h3>
  <h3 id="family_name"></h3>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script>
    $().ready(() => {
      $.get("/api/user")
        .then((res) => {
          $("#name").text("name:" + res.user.name);
          $("#email").text("email:" + res.user.email);
          $("#given_name").text("given_name:" + res.user.given_name);
          $("#family_name").text("family_name:" + res.user.family_name);
          $("#logout").show();
          $("#login").hide();
        });
      });
  </script>
</body>

</html>

```

#まとめ

App IDを使うと画面作成もDBも不要で、簡単にユーザー認証が組み込め流ことが、ご理解いただけたかと思います。

と言いつつ、実業務では、ログイン画面は自作するとか、ユーザー情報に項目追加したいとか、ユーザー管理用画面も別に必要だなど、さまざまなカスタマイズ要件が想定されます。

次回以降、それらの対応例も(可能であれば)投稿していきたいと思います。