備忘録 auth0+vue.js サインアップ/ログイン/ログアウト クイックスタート①

仕事でauth0を触る機会があり、調べ事しながら今後忘れないようにクイックスタートをやった記録として基本形をメモ程度に投稿させて頂きます。

# 準備・環境
auth0への登録（2020年10月4日現在は、22日間は無料で機能を試せる。）
vue-cli 4.4.1

```
# vue projectの作成
vue create my-app

# vue-routerのインストール
npm install vue-router

# auth0クライアントSDKをインストール
npm install @auth0/auth0-spa-js
```

# ディレクトリ
```
root/ 
 ├ node_modules/
 ├ public/
 ├ src/
 │  └ auth0/
 │  │ └ index.js
 │  │ └ authGuard.js
 │  └ views/
 │     └ Home.vue
 │     └ Profile.vue   
 ├ APP.vue
 ├ auth_config.json
 ├ main.js
 └ router.js
```
・auth0/index.js
ユーザー認証に必要なタスクを管理および調整する最良の方法は、Auth0SDKの周りに再利用可能なラッパーオブジェクトを作成する。
・auth0/authGuard.js
認証されていないユーザーがルートにアクセスするのを防ぐ機能を実装する。

# auth0側での設定

auth0にログインしてダッシュボードを確認する。
![auth0-1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308474/4e06fb84-bbc4-ac8d-768d-107b10021cb0.png)

アプリケーションの作成をクリック。

![auth0-2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308474/a6512d23-9a35-b32a-2d33-e38091c4a991.png)

適当にアプリ名を入力。

![auth0-3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308474/496b04b6-9d05-09e3-8c5c-c70d6ac8c01d.png)

作成するアプリに対応するタイプを選択する。今回はVue.jsを使用するのでシングルページWebアプリケーションを選択後、作成するをクリック。

アプリケーションが作成されるので、タブの設定をクリック。

![auth0-4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308474/029944c9-4535-2578-b7d9-ed273643dbab.png)

名前にはアプリ名が入る。今後この記事で必要なのはドメインとクライアントIDなのでメモしておく。

そのまま、下にスクロールしていきアプリのURIの登録を行う。
![auth0-5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308474/9109030a-a623-035e-ef78-9d445f4b65a6.png)
![auth0-6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308474/804e3443-8558-12f8-65e5-dfcc80e67294.png)

・Allowed Callback URLs
・Allowed Logout URLs
・Allowed Web Origins

上記の3項目に、http://localhost:8080 を入力して、最下段のsave changesボタンをクリック。vue プロジェクトをnpm run serveした際のポートに適宜変更して下さい。

# Views

``` Views/Home.vue
<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <div v-if="!$auth.loading">
      <!-- show login when not authenticated -->
      <button v-if="!$auth.isAuthenticated" @click="login">Log in</button>
      <!-- show logout when authenticated -->
      <button v-if="$auth.isAuthenticated" @click="logout">Log out</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Home',
  props: {
  },
  methods: {
    // Log the user in
    login() {
      this.$auth.loginWithRedirect();
    },
    // Log the user out
    logout() {
      this.$auth.logout({
        returnTo: window.location.origin
      });
    }
  }
}
</script>

```

```Views/Profile.vue
<template>
  <div class="profile">
    <div>
      <img :src="$auth.user.picture">
      <h2>{{ $auth.user.name }}</h2>
      <p>{{ $auth.user.email }}</p>
    </div>

    <div>
      <pre>{{ JSON.stringify($auth.user, null, 2) }}</pre>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Profile',
}
</script>

```
Home.vueにはログインボタンを配置。v-ifで条件分岐して\$auth.isAutenticatedを確認してログインしていれば、ログアウトボタン。ログインしていなければ、ログインボタンを表示する。
Profile.vueはログイン後に、ユーザ情報を確認するページとなり、ログインしているユーザ情報は\$auth.user.name $auth.user.emailで参照できる。

# auth0

```auth0/index.js
import Vue from "vue";
import createAuth0Client from "@auth0/auth0-spa-js";

/** Define a default action to perform after authentication */
const DEFAULT_REDIRECT_CALLBACK = () =>
  window.history.replaceState({}, document.title, window.location.pathname);

let instance;

/** Returns the current instance of the SDK */
export const getInstance = () => instance;

/** Creates an instance of the Auth0 SDK. If one has already been created, it returns that instance */
export const useAuth0 = ({
  onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
  redirectUri = window.location.origin,
  ...options
}) => {
  if (instance) return instance;

  // The 'instance' is simply a Vue object
  instance = new Vue({
    data() {
      return {
        loading: true,
        isAuthenticated: false,
        user: {},
        auth0Client: null,
        popupOpen: false,
        error: null
      };
    },
    methods: {
      /** Authenticates the user using a popup window */
      async loginWithPopup(o) {
        this.popupOpen = true;

        try {
          await this.auth0Client.loginWithPopup(o);
        } catch (e) {
          // eslint-disable-next-line
          console.error(e);
        } finally {
          this.popupOpen = false;
        }

        this.user = await this.auth0Client.getUser();
        this.isAuthenticated = true;
      },
      /** Handles the callback when logging in using a redirect */
      async handleRedirectCallback() {
        this.loading = true;
        try {
          await this.auth0Client.handleRedirectCallback();
          this.user = await this.auth0Client.getUser();
          this.isAuthenticated = true;
        } catch (e) {
          this.error = e;
        } finally {
          this.loading = false;
        }
      },
      /** Authenticates the user using the redirect method */
      loginWithRedirect(o) {
        return this.auth0Client.loginWithRedirect(o);
      },
      /** Returns all the claims present in the ID token */
      getIdTokenClaims(o) {
        return this.auth0Client.getIdTokenClaims(o);
      },
      /** Returns the access token. If the token is invalid or missing, a new one is retrieved */
      getTokenSilently(o) {
        return this.auth0Client.getTokenSilently(o);
      },
      /** Gets the access token using a popup window */

      getTokenWithPopup(o) {
        return this.auth0Client.getTokenWithPopup(o);
      },
      /** Logs the user out and removes their session on the authorization server */
      logout(o) {
        return this.auth0Client.logout(o);
      }
    },
    /** Use this lifecycle method to instantiate the SDK client */
    async created() {
      // Create a new instance of the SDK client using members of the given options object
      this.auth0Client = await createAuth0Client({
        domain: options.domain,
        client_id: options.clientId,
        audience: options.audience,
        redirect_uri: redirectUri
      });

      try {
        // If the user is returning to the app after authentication..
        if (
          window.location.search.includes("code=") &&
          window.location.search.includes("state=")
        ) {
          // handle the redirect and retrieve tokens
          const { appState } = await this.auth0Client.handleRedirectCallback();

          // Notify subscribers that the redirect callback has happened, passing the appState
          // (useful for retrieving any pre-authentication state)
          onRedirectCallback(appState);
        }
      } catch (e) {
        this.error = e;
      } finally {
        // Initialize our internal authentication state
        this.isAuthenticated = await this.auth0Client.isAuthenticated();
        this.user = await this.auth0Client.getUser();
        this.loading = false;
      }
    }
  });

  return instance;
};

// Create a simple Vue plugin to expose the wrapper object throughout the application
export const Auth0Plugin = {
  install(Vue, options) {
    Vue.prototype.$auth = useAuth0(options);
  }
};

```

```auth0/authGuard.js
import { getInstance } from "./index";

export const authGuard = (to, from, next) => {
  const authService = getInstance();

  const fn = () => {
    // If the user is authenticated, continue with the route
    if (authService.isAuthenticated) {
      return next();
    }

    // Otherwise, log in
    authService.loginWithRedirect({ appState: { targetUrl: to.fullPath } });
  };

  // If loading has already finished, check our auth state using `fn()`
  if (!authService.loading) {
    return fn();
  }

  // Watch for the loading property to change before we check isAuthenticated
  authService.$watch("loading", loading => {
    if (loading === false) {
      return fn();
    }
  });
};

```
この2ファイルは取り合えず公式サンプルをそのまま使用。

# auth_config.json

```auth_config.json
{
  "domain": "your domain",
  "clientId": "your clientId"
}

```

auth0のアプリ設定でメモしていたdomainメイトclientIdをauth_config.jsonに記述する。

# router.js

```router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './views/Home'
import Profile from './views/Profile'
import { authGuard } from './auth0/authGuard';

Vue.use(VueRouter)

const router = new VueRouter({
    mode: 'history',
    routes: [
      {
        path: '/',
        name: 'home',
        component: Home
      },
      {
        path: '/profile',
        name: 'profile',
        component: Profile,
        beforeEnter: authGuard
      }
    ]
  });
  
  export default router;

```

# main.js

```main.js
import Vue from "vue";
import App from "./App.vue";
import router from './router'

// Import the Auth0 configuration
import { domain, clientId } from "./auth_config.json";

// Import the plugin here
import { Auth0Plugin } from "./auth0";

// Install the authentication plugin here
Vue.use(Auth0Plugin, {
  domain,
  clientId,
  onRedirectCallback: appState => {
    router.push(
      appState && appState.targetUrl
        ? appState.targetUrl
        : window.location.pathname
    );
  }
});

Vue.config.productionTip = false;

new Vue({
  router,
  render: h => h(App)
}).$mount("#app");

```

# App.vue
``` App.vue
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link>|
      <router-link to="/about">About</router-link>|
      <router-link v-if="$auth.isAuthenticated" to="/profile">Profile</router-link>
    </div>
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App',
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

```

auth0のクイックスタートを素直にやってみました。色々調べると出来る事も多そうなので今後も勉強していきたいです。

最終目標は、社内のアクティブディレクトリと連携してログイン機能を実装する事ですが、取り急ぎはautu0を試せたので良かったです。アクティブディレクトリとの連携はまた、出来たら忘れないように記事を書きたいと思います。

参考：https://auth0.com/docs/quickstart/spa/vuejs