11
7

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 3 years have passed since last update.

Auth0を触ってみた

Last updated at Posted at 2019-12-19

本記事はうるる Advent Calendar 2019 20日目の記事です。

はじめに

Webサービスを提供する際には認証・認可の機能は殆どの場合で必須になります。
しかし、自前でこの仕組みを実装しようとなるとけっこう大変です。加えて、セキュアに実装をしなければならなかったり、ソーシャルログインに対応したりなど、、、やることは山積みです。

そこで、今回はAuth0というサービスを使って、ソーシャルログインの実装とAPIに対する認証機能を実装したいと思います。

開発環境を構築する

前提

  • API : NestJS
    • ライブラリ
      • passport
      • @nestjs/passport
      • passport-jwt
      • jwks-rsa
  • クライアント : Nuxt.js
    • ライブラリ
      • @nuxtjs/auth
  • 開発環境 : Docker Compose

今回使用する開発環境はこんな感じです。
フレームワークなどは完全に筆者の好みです。
また、フレームワークのインストールとライブラリのインストールは済ませている状態です。

docker-compose.yml
version: '3'
services:
  api:
    build:
      context: ./api
    volumes:
      - ./api:/api
    ports:
      - 3210:3000
  app:
    build:
      context: ./app
    volumes:
      - ./app:/app
    ports:
      - 8888:3000

# ディレクトリ構成
    .
    ├── api
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── dist
    │   ├── nest-cli.json
    │   ├── node_modules
    │   ├── package-lock.json
    │   ├── package.json
    │   ├── src
    │   ├── test
    │   ├── tsconfig.build.json
    │   ├── tsconfig.json
    │   └── tslint.json
    ├── app
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── assets
    │   ├── components
    │   ├── jest.config.js
    │   ├── jsconfig.json
    │   ├── layouts
    │   ├── middleware
    │   ├── node_modules
    │   ├── nuxt.config.js
    │   ├── package-lock.json
    │   ├── package.json
    │   ├── pages
    │   ├── plugins
    │   ├── static
    │   ├── store
    │   └── test
    └── docker-compose.yml

また、NestJSとNuxt.jsそれぞれのDockerfileはこんな感じです。
(Dockerfileを使ってビルドをしましたが、もしかしたらいらなかったかもしれません、、、。)

api/Dockerfile
FROM node:12-alpine

WORKDIR /api

ADD package.json package-lock.json ./
RUN npm i

ADD . ./

CMD ["npm", "run", "start:dev"]
app/Dockerfile
FROM node:12-alpine

WORKDIR /app

ADD package.json package-lock.json ./
RUN npm i

ADD . ./

CMD ["npm", "run", "dev"]

NestJSでAPIを実装する

APIの実装

ではまず認証機能を実装するAPIの実装をしていきたいと思います。

NestJSのデフォルト設定では、src配下に*.tsファイルを配置して行くことでdistのディレクトリにそれを*.jsにコンパイルして配置してくれます。

早速、userというディレクトリを作成して各種ファイルを配置していきましょう。

user/user.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('user')
export class UserController {
  constructor() {}

  @Get()
  list() {
    return [
      { id: 1, name: 'Alpha' },
      { id: 2, name: 'Bravo' },
      { id: 3, name: 'Charlie' },
    ];
  }
}
user/user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';

@Module({
  imports: [],
  controllers: [UserController],
  providers: [],
})
export class UserModule {}
user/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './use/user.module';

@Module({
  imports: [UserModule], // UserModuleをimport
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

ここでやっていることは簡単で、http://localhost:3210/api/userにアクセスするとユーザーのリストが返ってくるAPIを作成しています。

NestJSは少なくとも1つのmoduleを持っている必要があり、moduleをもとにClass間の依存関係を解決します。初期状態ではapp.module.tsのみが配置されており、これをルートモジュールと呼ぶそうです。
基本的にはリソース単位でモジュールを作成することになると思います。

今回はuser.module.tsを作成してapp.module.tsのimportsにUserModuleを記述します。
※ 今回はやりませんでしたが、 cliでserviceやcontrollerを作成すると、自動的にAppModuleのproviderscontrollersに挿入してくれるようです。

Postmanなどでアクセスしてみると、下記のような結果を受け取ることができます。
postman result

ここで一旦、APIの実装は終わりです。

Auth0

Auth0とは

ユニバーサルログインやSSO、MFAなんかを比較的簡単に実装できる認証・認可のプラットフォームです。

IDaaSとも言うらしいです。類似サービスとして、Firebase Authentication や AWS Cognitoなどが挙げられます。

個人的に嬉しかったのはサンプルコードを公式が用意してくれていることです。
また、SDKやドキュメントも豊富にあって入門しやすい印象でした。

Auth0 Applicationsの作成

Auth0を使ってクライアント側からログインをしたり、jwtトークンの発行などをする場合は、Applicationsの作成が必要になります。
そのためにまず、ダッシュボードでサイドメニューのApplicationsを選択して、CREATE APPLICATIONというボタンを押します。typeは様々選べるのですが、Nuxt.jsはSPAモードを選択するので、Single Page Web Applicationを選択してください。Auth0 Create Application

そして、各種入力欄に下記のように入力して、ページ下部にあるSAVE CHANGEボタンを押してください。

Allowed Callback URLs : http://localhost:8888/callback
Allowed Web Origins : http://localhost:8888
Allowed Origins (CORS) : http://localhost:3210

加えて、Default Audienceの設定を行います。
入力するのは、Allowed Origins (CORS)と同じ値です。
こちらは本来であれば設定をしなくてよいのですが、Nuxt.jsの仕様で設定をしないとうまく動きません。

https://manage.auth0.com/dashboard/us/dev-<自分のID>/tenant/general のページで、設定できますので忘れないように設定してください。

これで、Auth0での設定は完了しました。

APIに認証機能を実装する

Auth0の設定も完了しましたので、先程実装したAPIに認証機能を実装していきたいと思います。
srcauthディレクトリを作成してAuthModuleJwtStrategyというクラスをそれぞれ実装してください。

auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';

@Module({
  imports: [PassportModule.register({ defaultStrategy: 'jwt' })],
  providers: [JwtStrategy],
  exports: [],
})
export class AuthModule {}

jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt, VerifiedCallback } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
  constructor() {
    super({
      secretOrKeyProvider: passportJwtSecret({
        cache: true,
        jwksRequestsPerMinute: 5,
        jwksUri: `https://dev-<自分のID>.auth0.com/.well-known/jwks.json`,
      }),
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      issuer: 'https://dev-<自分のID>.auth0.com/',
      audience: 'http://localhost:3210', // apiサーバーのドメイン
      algorithms: ['RS256'],
    });
  }

  async validate(payload: any, done: VerifiedCallback) {
    if (!payload) {
      done(new UnauthorizedException(), false);
    }

    return done(null, payload);
  }
}

これらを実装したら、app.module.tsを下記のように修正します。

app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './use/user.module';
import { AuthModule } from './auth/auth.module';

@Module({
  imports: [UserModule, AuthModule], // AuthModuleを追加
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

そして、user.controller.tsを下記のように修正します。

user.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('users')
@UseGuards(AuthGuard('jwt')) // Guardsを追加
export class UserController {
  constructor() {}

  @Get()
  list() {
    return [
      { id: 1, name: 'Alpha', age: 12 },
      { id: 2, name: 'Bravo', age: 24 },
      { id: 3, name: 'Charlie', age: 36 },
    ];
  }
}

@UseGuards(AuthGuard('jwt'))
を追加することで、このコントローラー内に作られるエンドポイントのAPIはすべて認証が必須となります。
(これはNestJSのGuardsという機能で、認証機能を実装する際によく使われるそうです。)

作成したAPIをPostmanなどで叩くと、401が返ってくることが確認できます。
Postma Result Not Auth

この認証を通すためにはAPIコールの際にJWTトークンを渡してやる必要があります。このJWTトークンはクライアント側でソーシャルログインをしたときに発行され、axiosでAPIコールする際にNuxtのモジュールがヘッダーに自動で差し込んでくれます。
なので、サーバーサイドは受け取ったトークンをどう処理するかだけを実装すればいいのです。

Nuxt.jsでクライアント側を実装する

nuxt.config.js 設定

続いて、Nuxt.jsでログイン機能を実装していきたいと思います。

各種設定はnuxt.config.jsに記述していくことになります。今回はaxiosauthなどの設定をしています。
domainclient_idに関しては、Auth0のApplication詳細ページにあるDomainClient IDを入力してください。

nuxt.config.js
...

modules: [
  '@nuxtjs/axios',
	'@nuxtjs/auth' // こいつが重要
],

axios: {
  host: 'localhost',
  port: 3210,
	prefix: '/api'
},

// Auth0 setting
  auth: {
    strategies: {
      auth0: {
        domain: '****************', // Applicationsの詳細ベージで確認
        client_id: '****************' // Applicationsの詳細ベージで確認
      }
    },
    redirect: {
      login: '/', // 未ログイン時のリダイレクト先
      logout: '/', // ログアウト処理を実行した直後のリダイレクト先
      callback: '/callback', // コールバックURL
      home: '/users' // ログイン後に遷移するページ
    }
  },

// 認証済みでないと各種ページにアクセスできないようにする
// index.vueとcallback.vueのページは認証無しでアクセス可
router: {
	middleware: ['auth']
},

...

各種ページを作成

最後に、各種ページを作成していきたいと思います。
まず、layout/default.vuepages/index.vueを下記のように修正します。

default.vue
<template>
  <v-app>
    <v-app-bar app>
      <v-toolbar-title v-text="title" />
      <v-spacer />
      <v-btn v-if="!this.$auth.$state.loggedIn" color="primary" @click="login">
        Login
      </v-btn>
      <v-btn v-else text @click="logout">Logout</v-btn>
    </v-app-bar>
    <v-content>
      <v-container>
        <nuxt />
      </v-container>
    </v-content>
  </v-app>
</template>

<script>
export default {
  data() {
    return {
      title: 'Auth0 Demo'
    }
  },

  methods: {
    login() {
      // authモジュールを使用してのログイン
      this.$auth.loginWith('auth0')
    },

    logout() {
      this.$auth.logout()
    }
  }
}
</script>
index.vue
<template>
  <v-layout justify-center>
    <v-card width="800px">
      <v-card-title primary-title>
        Let's Login !!!
      </v-card-title>
    </v-card>
  </v-layout>
</template>

こうすることで、下記のようなページが出来上がります。
top page before login

次に、pages/callback.vueを作成します。
こちらのページは、Auth0からコールバックされるページの作成です。
許可コードを発行する際に一瞬だけ表示されるページですので、templateのみにしています。

callback.vue
<template>
  <div>
    <p>Please Wating</p>
  </div>
</template>

最後に、ログイン後に遷移するユーザーの一覧ページです。

users.vue
<template>
  <v-layout justify-center>
    <v-data-table :headers="headers" :items="users" item-key="id">
    </v-data-table>
  </v-layout>
</template>

<script>
export default {
  data() {
    return {
      headers: [
        { text: 'id', value: 'id' },
        { text: 'name', value: 'name' },
        { text: 'age', value: 'age' }
      ],
      users: []
    }
  },

  created() {
    // APIコールの際に自動でJWTトークンをヘッダーのAuthorizationにつけて送信してくれます。
    this.users = await this.$axios.$get('users')
  }
}
</script>

ここまで実装が完了すると、Nuxt.jsでの実装も一通り完了です!

とりあえず、うまく動くか確認してみましょう。
まずは、http://localhost:8888/にアクセスしてみましょう。
そして、右上のログインボタンを押してみるとこんな見た目のページに遷移すると思います。

ここで、「Googleで続ける」を選択してみてください。
そうすると、現在のブラウザでログインしているGoogleのアカウント一覧が表示されたと思います。
その中から、ログインしたいアカウントを選択して、ログインが成功すると自分が作ったウェブサイトのhttp://localhost:8888/usersに遷移すると思います。
Auth0 login form
ログイン後に、遷移するページは、http://localhost:8888/usersとなります。
このページでは、しっかりとAPIの認証も完了してデータを取ってくるとができています。
users list
ちなみに、こんな感じでBearerトークンとして挿入してくれています。
ちゃんと認証も通っていることが確認できます。
bearer token

ひとこと

Auth0を使うことで、こんなに簡単に認証が実装できてしまうのは驚きました。
また、煩わしいソーシャルログインの仕組みもAuth0がすべて肩代わりしてくれるので、開発体験が最高でした。
今回はGoogleのソーシャルログイン機能のみを試しましたが、もっと多くの機能がAuth0にはあるので、気になった方は是非試してみてください!!

11
7
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
11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?