1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

IBM Cloud App IDを使ってCode Engineのアプリにログインを実装してみる

Posted at

はじめに

Webアプリケーションを作る時には必須になるアカウント管理とそれに伴うログイン/ログアウト処理ですが、実際に実装するとなるとセキュリティ事故を起こさないようかなり注意深く実装を行う必要があり、可能な限り既にある実装・サービスを利用したいところです。IBM CloudにおいてもApp IDというサービスが提供されており、今回はこれを利用して実装を行ってみます。基本的には公式ドキュメントに従って実装するだけですが、実際にCode Engineにデプロイするまでの手順を行うことで気づいた注意点などを記していこうと思います。

App IDを構成

まずはApp IDのインスタンスをprovisionします。こちらも無料枠があるので簡単に試す程度であれば費用はほとんど発生しないと思います。
スクリーンショット 2021-12-28 16.49.16.png

認証処理を行う際、App IDの認可サーバーでユーザーがログインした後アプリケーションにリダイレクトされますが、その時のURLを設定する必要があります。App IDの管理画面の「認証の管理」→「認証設定」で以下のようなリダイレクトURLを設定します。

スクリーンショット 2021-12-28 20.40.05.png

実際にApplicationのURLが決まったらここに追加する必要がありますが、まずはローカル環境のみ指定しておきます。

Node.jsでのApp IDの利用

Node.jsでApp IDを使うために以下のようなモジュールをインストールします。

$ npm install --save express express-session passport pug ibmcloud-appid

app.jsでは以下のように指定します。

app.js(定義部分)
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
app.js(初期化部分)
app.use(session({
   secret: '123456',
   resave: true,
   saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, cb) => cb(null, user));
passport.deserializeUser((user, cb) => cb(null, user));
passport.use(new WebAppStrategy({
   tenantId: "{tenant_ID}",
   clientId: "{client_ID}",
   secret: "{secret}",
   oauthServerUrl: "{OAuth_Server_URL}",
   redirectUri: "http://localhost:3000/auth/authenticate"
}));

このままではsessionの扱いに問題がありますが、今回は動作確認なのでこのままにしておきます。tenant_ID等の値はApp IDの「アプリケーション」のところにあるcredentialsから適宜コピーしておきます。

さらにログイン・ログアウト用のエンドポイントも定義しておきます。

app.js(エンドポイント)
app.get('/auth/login', passport.authenticate(WebAppStrategy.STRATEGY_NAME, {
  successRedirect: '/',
  forceLogin: true
}));
app.get('/auth/authenticate', passport.authenticate(WebAppStrategy.STRATEGY_NAME));
app.get('/auth/logout', (req, res) =>{
  WebAppStrategy.logout(req);
  res.redirect('/');
});

ブラウザで/auth/loginにアクセスするとApp IDのログイン画面にリダイレクトされ、そこでログイン情報を入力して認証されると/auth/authenticateにリダイレクトされ、ログインした状態でアプリケーションが利用できます。同様に/auth/logoutにアクセスするとログアウトしてからトップページにリダイレクトされることになります。

Dev serverにおけるproxy設定

前回の記事client/package.jsonproxyの設定をしましたが、それはdev serverを使用しているときにtext/html以外のリクエストが来た場合にproxyに指定したURLに引き渡すという設定でした。しかし、上記の設定をしたexpressサーバーに/auth/loginのようなリクエストを投げたくても、そのリクエストはtext/htmlなので(App IDが提供してくれるログイン画面なので)dev serverが受け取ってしまい、トップページが表示されてしまいます。試しにhttp://localhost:3000/auth/loginを指定するとトップページが表示されることがわかると思います。逆に直接expressサーバーの方(http://localhost:8080/auth/login)にアクセスするとログイン画面が表示されると思います。
スクリーンショット 2021-12-28 20.52.46.png

なのでプロダクションビルドでは問題ないですが、開発中はReactアプリの方からはログインできないことになるのでかなり面倒です。この問題はproxyをpackage.jsonでの指定ではなくちゃんと構成してやることで解決します。詳細な手順等はこちらを参照してください。

まずはhttp-proxy-middlewareを導入します。clientディレクトリで以下を実行します。

$ npm install http-proxy-middleware --save

次にsrc/setupProxy.jsを作成します。

src/setupProxy.js
const {createProxyMiddleware} = require('http-proxy-middleware');

module.exports = function(app) {
    app.use('/api', createProxyMiddleware({
        target: 'http://localhost:8080',
        changeOrigin: true
    }));
    app.use('/auth', createProxyMiddleware({
        target: 'http://localhost:8080',
        changeOrigin: true
    }));
}

(今後のため、前回/usersでルーティングしていたAPIは/api以下に変更しました。)

この状態でdev serverを起動すると、/api/authに関してはproxyされexpressサーバーの方へリクエストが渡されます。試しに http://localhost:3000/auth/loginにアクセスすると先ほどと同様のログイン画面が表示されることがわかると思います。

APIでのユーザー情報の取得

ログインが正常に完了すると、req.user以下にログイン情報が渡されてきます。routes/users.jsを以下のように変更します。

routes/users.js
router.get('/', function(req, res, next) {
  res.json({
    login: !!req.user,
    given_name: req.user?.given_name
  });
});

これを呼び出すclient/src/App.jsの方では、まずAPI呼び出しのところをjsonを受け取るように変更します。

client/src/App.js
  const [users, setUsers] = useState('');
  useEffect(() => {
    fetch('/api')
      .then((res) => res.json())
      .then((json) => setUsers(json));
  }, []);

メッセージの表示部分は以下のように変更します。

client/src/App.js
        <p>
          Hello {users.given_name || "Unknown User"}!
        </p>
        <p>
          {users.login &&
            <a className="App-link" href="/auth/logout">Logout</a> ||
            <a className="App-link" href="/auth/login">Login</a>
          }
        </p>

SPAではこのやり方は良くないのかもしれませんが、App IDがログインページを用意してくれているので、とりあえずページ遷移する実装を採用しています。もっと良い方法があるかも知れませんので実装する際は自己責任でお願いします。

実行してみます。
スクリーンショット 2021-12-28 21.09.41.png
ログイン前なのでまずはUnknown Userとして表示され、"Login"のリンクが表示されています。ログインを押すとApp IDのログインページが表示されます。ここでLogin with Googleか先ほどApp IDの管理画面で登録したユーザーかどちらかでログインすると(Login with Facebookはアプリケーションの登録が必要そうでした)、以下の画面に遷移するのがわかると思います。

スクリーンショット 2021-12-28 21.12.04.png

"Logout"のリンクをクリックするとまた最初の画面に戻ります。

設定情報の構成

基本的には前回と同じ方法でCode Engineにデプロイ可能ですが、一つ気をつけなければいけないのはApp IDのcredentialsをGitHubにはpushできない点です。ですので開発環境ではローカルのファイルを参照し、クラウド環境ではソースコードではなく別のところから設定する必要があります。

いくつかやり方はありますが、今回はnode-config(モジュール名はconfig)を使います。node-configは環境変数(NODE_ENV)に従って階層的にファイルから設定情報を読んでくれるモジュールです。公式ドキュメントによると以下のような順番で読み込まれます。

default.EXT
default-{instance}.EXT
{deployment}.EXT
{deployment}-{instance}.EXT
{short_hostname}.EXT
{short_hostname}-{instance}.EXT
{short_hostname}-{deployment}.EXT
{short_hostname}-{deployment}-{instance}.EXT
{full_hostname}.EXT
{full_hostname}-{instance}.EXT
{full_hostname}-{deployment}.EXT
{full_hostname}-{deployment}-{instance}.EXT
local.EXT
local-{instance}.EXT
local-{deployment}.EXT
local-{deployment}-{instance}.EXT
(Finally, custom environment variables can override all files)

{deployment}の部分がNODE_ENVで指定される部分になります。これを利用して、以下のファイル群を作成します。

config/default.json
config/production.json
config/local.json
config/custom-environment-variables.json

ここでlocal*はGitHubにpushしないものなので.gitignoreに追加しておきます。

.gitignore
/config/local*

ファイルの中身は以下のようにします。まずdefault.jsonには以降のファイルで指定しなかった場合に使われるデフォルトの値を指定します。ここでは開発環境のurlだけを指定しておきます。

config/default.json
{
    "appid": {},
    "url": "http://localhost:3000"
}

次にproduction.jsonにはクラウド環境で実行した時に使われる値を指定します。今回は特に記述できるものがないのでplaceholderのみです。

config/production.json
{
    "appid": {}
}

local.jsonはGitHubにはpushしないファイルで、ローカル環境で参照する値を記述しておきます。このappidの中にはIBM Cloudコンソールからコピーしたcredentialsをそのままペーストしておきます。

config/local.json
{
    "appid": {
        "clientId": "...",
        "tenantId": "...",
        "secret": "...",
        "name": "react-express",
        "oAuthServerUrl": "https://...",
        "profilesUrl": "https://...",
        "discoveryEndpoint": "https://...",
        "type": "regularwebapp",
        "scopes": []
    }
}

クラウド環境にデプロイしたあとは、環境変数を経由してcredentialsを読み込む必要があります。1つ1つ指定してもいいのですが、項目数が結構あるので、App IDのcredentialsのjsonを1つの環境変数を通じて読み込みます。custom-environment-variables.jsonを使うと環境変数のパースをして取り込むことができます。APPID_CREDENTIALSからjsonを読み込む指定方法は以下になります。また、アプリケーションのURLは単純にAPPLICATION_URLとして指定できるように記述します。

config/custom-environment-variables.json
{
    "appid": {
        "__name": "APPID_CREDENTIALS",
        "__format": "json"
    },
    "url": "APPLICATION_URL"
}

configを使ってアプリケーションを初期化するようにapp.jsを変更をします。

javascript/app.js
...

const config = require('config');

...

passport.use(new WebAppStrategy({
  tenantId: config.get('appid.tenantId'),
  clientId: config.get('appid.clientId'),
  secret: config.get('appid.secret'),
  oauthServerUrl: config.get('appid.oAuthServerUrl'),
  redirectUri: `${config.get('url')}/auth/authenticate`
}));

(なぜかは不明ですが、WebAppStrategyに与えるプロパティ名はoauthServerUrlですが、jsonの方はoAuthServerUrlとなっていますので注意してください。)

この状態でローカルでログイン・ログアウトが可能なことを確認し、GitHubにもpushしておきます。

Code Engineへのデプロイ

では早速アプリケーションをデプロイしてみます。custom-environment-variables.jsonに指定したように、APPID_CREDENTIALSにApp ID用のcredentialsをセットする必要があります。Code Engineのプロジェクトの画面から、「シークレットおよびconfigmap」を選択し、シークレットを設定しておきます。
スクリーンショット 2022-01-03 11.22.59.png

次にアプリケーションをデプロイします。前回と同様にリポジトリの指定等をしてから環境変数を指定します。
スクリーンショット 2022-01-03 11.30.48.png

またリダイレクトURLを構築するためのAPPLICATION_URLも指定する必要があります。どのようなURLになるのか不明であれば、一度デプロイしてから構成の変更から指定してデプロイし直す方法もあります。デプロイが完了するのを待ってアプリケーションにアクセスし、ログイン・ログアウトが正常に完了することを確認します。

まとめ

IBM Cloud Code EngineでApp IDを使ったログイン・ログアウトの実装方法についてまとめました。App IDを使うと手間のかかるアカウント周りの実装を行う必要がないので便利にサービスを実装できます。

参考

ソースコード: https://github.com/fterui/react-express (次の記事用のコードも含みます)

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?