SPA の localhost 起動時、 Auth0 認証経由の API 実行が Consent Required エラーになってしまうのを回避する

# はじめに

以前書いた、[Auth0 サンプルの SPA を微修正して、 Auth0 サンプルの API に CORS していく](https://qiita.com/keiichi-hikita/items/f06e36aa6fb2e670fc00) では、Auth0 のログイン設定にて audience 設定をしていました。

```javascript:src/main.ts
app
  .use(hljsVuePlugin)
  .use(createRouter(app))
  .use(
    createAuth0({
      domain: authConfig.domain,
      clientId: authConfig.clientId,
      authorizationParams: {
        redirect_uri: window.location.origin,
        audience: authConfig.audience // <-- 追加してたとこ
      }
    })
  )
```

しかし、ふと疑問が。

「では単一のページ (SPA) から複数の対向 API 実行したかったらどうするんだ？」
(当然発生するケース)

で、それ自体は簡単だったのですが、今度はそれを `localhost` で検証する場合に若干面倒だということがわかったので、その解消方法について備忘録がてら残しておこうと思います。


# 複数の対向 API を実行したい場合に何を変更すればよいか？

例えば前回の例だと `http://localhost:3000` にて vite で起動したサーバーから、 `http://localhost:8080` のみに API実行してました。

今回は、 `http://locahlhost:8080` , `http://locahlhost:8081` の2箇所に API 実行したいと思います。

で、結論から書くと、実装自体は非常にシンプルです。以下でできます。


```javascript:src/main.ts
app
  .use(hljsVuePlugin)
  .use(createRouter(app))
  .use(
    createAuth0({
      domain: authConfig.domain,
      clientId: authConfig.clientId,
      authorizationParams: {
        redirect_uri: window.location.origin,
        // audience: authConfig.audience <-- ここを削除
      }
    })
  )
```

↓はベタなコードですいません。サンプルの改造に力入れてもしょうがないんで :sweat: 

```vue:src/views/Practice.vue
<template>
  <div>
    {{ token }}
    <hr/>
    Response from 8080: {{ response1 }}
    <hr/>
    Response from 8081: {{ response2 }}
  </div>  
</template>

<script setup lang="ts">
import { onMounted } from 'vue';
import { useAuth0 } from '@auth0/auth0-vue'
import { ref } from 'vue'
import axios from 'axios'

const { getAccessTokenSilently } = useAuth0()
const token = ref('')
const response1 = ref({})
const response2 = ref({})
const options = ref({})

import authConfig from "../../auth_config.json";
onMounted(async () => {
  token.value = await getAccessTokenSilently({
    authorizationParams: {
        audience: authConfig.audience_1
    }
  })
  options.value = {
    headers: {
      Authorization: `Bearer ${token.value}`
    }
  }

  var temp = await axios.get('http://localhost:8080/authorized', options.value)
  response1.value = temp.data.message

  token.value = await getAccessTokenSilently({
    authorizationParams: {
        audience: authConfig.audience_2
    }
  })
  options.value = {
    headers: {
      Authorization: `Bearer ${token.value}`
    }
  }

  temp = await axios.get('http://localhost:8081/authorized', options.value)
  response2.value = temp.data.message
})
</script>
```

[getAccessTokenSilently](https://auth0.github.io/auth0-vue/interfaces/GetTokenSilentlyOptions.html) の `AuthorizationParams` を書いてあげるだけですね。


しかしながら、これを単に実行すると `Consent Required` というエラーになり、2回目の API (to `localhost:8081` がエラーになるはずです。

で、これをどうやって解消するんだ？...って話です。

# 解決策は `https` 化して、かつ `localhost` でなくアクセスすれば OK らしい

本現象の根本原因は [こちら](https://auth0.com/docs/get-started/applications/confidential-and-public-applications/user-consent-and-third-party-applications) のようです。

どうやら初回はログイン済みセッションから OAuth 同意画面を SKIP できているようですが、その次の同意画面をSKIP できないということなのかなと解釈しました。
(first party, third party ってのは OAuth の用語なのか Auth0 の用語なのかよくわかってませんが。なんにしてももうちょっと勉強しないとです...)

では上記のページに記載があるように `/etc/hosts` とかにエントリ追加して `localhost` じゃなくせばいいじゃない？となるんですが、今度はこちらが問題となります。

[Why do I get auth0-spa-js must run on a secure origin?](https://github.com/auth0/auth0-spa-js/blob/main/FAQ.md#why-do-i-get-auth0-spa-js-must-run-on-a-secure-origin)

これをサマリすると、 `auth0-spa-js` が動くためには、以下の URL じゃなきゃだめだよと言ってます。

- `https` なURLでの接続
- `localhost` 的なニュアンスのなにか

これと先程の課題の AND を取ると、

「 `https` かつ `localhost` じゃない URL でアクセスすればいいじゃない」ということになりますね。

ということで、以下の対応をしました。

# `/etc/hosts` にエントリ追加

どうせならさっきのページに書かれた感じでエントリ追加してみましょう。

```
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1	myapp.example <-- 追加
255.255.255.255	broadcasthost
::1             localhost
```

# `mkcert` を使って `https` 化

[viteでhttpsなlocalhostを起動する](https://dev.classmethod.jp/articles/vite-https-localhost/) が大変参考になりました。ありがとうございます。

やったことは以下です。

```bash
% brew install mkcert

% cd /path/to/SPA のある場所

% mkcert -install
Created a new local CA 💥
Sudo password:
The local CA is now installed in the system trust store! ⚡️

% mkcert localhost

Created a new certificate valid for the following names 📜
 - "localhost"

The certificate is at "./localhost.pem" and the key at "./localhost-key.pem" ✅

It will expire on 1 May 2027 🗓

% ls *.pem
localhost-key.pem  localhost.pem
```

ここで作成されたファイルを、 SPA の `vite.config.js` に読み込みます。

```javascript:vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import fs from 'fs' // <-- 追加

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  // ここから下を追加
  server: {
    https: {
      key: fs.readFileSync('./localhost-key.pem'),
      cert: fs.readFileSync('./localhost.pem'),
    }
  }
})

```

以下も実施。

```
npm install --save-dev @types/node
```

最後に Auth0 Dashboard で、SPA に関する以下の URL を書き換えておきます。
(もともとは `http://localhost:3000` になっているはず)

- Allowed Callback URLs
- Allowed Logout URLs
- Allowed Web Origin


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/7d5c30c1-43b3-fa1d-b113-2c44812d479a.png)

ここまで完了したら、8080, 8081 の API と、SPA の vite を起動し、ブラウザから `https://myapp.example:3000/#/practice` に接続します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/9bb7cbef-1f39-b01b-46e7-1f984a2a6d42.png)

`Consent Required` が回避できているのがわかります。


# まとめ

ということで、複数の対向 API を持つ SPA を `localhost` で開発する際に発生する `Consent Requied` 問題が発生することを、以下にて回避することができました。

- audience 定義場所の変更
- hosts へのエントリ追加
- vite サーバーの https 化

次はユーザアクセスのブロックや RBAC 周りを勉強していこうと思います。