この記事の目的
Nuxt.jsアプリのAuth Module使ってログイン機能を実装するための記事です。
また、記事の最後にはRuby on Railsのdevise_token_auth
を使ってトークンベースで認証する方法も記載しています。
前提
- Nuxt.jsのアプリが用意されていること
- Nuxt.jsアプリとログインAPI用のエンドポイントは
POST
メソッド・/auth/sign_in
とする
ログインの流れ
- 【ユーザ】 Nuxtアプリのログイン画面でEメールとパスワードを入力し、「送信」ボタンを押す
-
【Nuxtアプリ】 パスワードとEメール情報を付与し、APIの
/auth/sign_in
にHTTPリクエストを送信する - 【API】 NuxtアプリからHTTPリクエストを受け取り、パスワードとEメールで認証をする。認証が完了し、HTTPレスポンスを返す
- 【Nuxtアプリ】 APIからHTTPレスポンスを受け取る
-
【Nuxtアプリ】 レスポンスヘッダー情報を
Vuex store
に保存する - 【Nuxtアプリ】 ユーザをログイン画面からホーム画面へリダイレクトする
-
【Nuxtアプリ】
Vuex store
を確認し、ログイン済みであればログインしていないとアクセスできないページへのアクセスを許可する。ログイン済みでなければ、ログインページにリダイレクトする。
事前知識
Auth Moduleとは
**Auth Module**とは、Nuxt.jsプロジェクトにJWT(JSON Web Tokens)またはOAuth認証を使ってログイン機能を簡単に実装できるライブラリです。
ログイン機能は一から実装すると色々なことに気を使わないといけなくて大変なのですが、複雑な仕事を担ってくれているのがこのAuth Moduleです。
JWTとは
JWT(JSON Web Token)とは、JSONというデータ構造で情報が表現されたフォーマットです。安全にデータを運ぶために使用されます。JWTの詳しい説明は、下記の記事がわかりやすいです。
・JSON Web Token の効用
・認証におけるJWTの利用について
ステップ
Axiosを導入
Axiosを使用するので、Axiosを導入していない場合は下記のコマンドを実行してください。
$ yarn add @nuxtjs/axios
または
$ npm install @nuxtjs/axios
Auth Moduleを導入
下記コマンドでAuth Moduleを導入します。
$ yarn add @nuxtjs/auth
または
$ npm install @nuxtjs/auth
Vuex store用にindex.js
ファイルを作成
Auth moduleはVuex storeを使ってユーザの情報やログイン情報を管理します。そのためVuex store用のファイルを用意しておく必要があります。nuxtアプリのルートディレクトリにVuex store
というディレクトがあるので、そこにindex.js
という名前のファイルを作成しておきます。index.js
の中はからっぽで大丈夫です。
nuxt.config.js
を修正
nuxt.config.js
のmodulesに下記を加えます。
modules: [
'@nuxtjs/axios',
'@nuxtjs/auth'
]
さらに、nuxt.config.js
に下記を追加します。
axios: {
baseURL: 'http:localhost:3000'
},
auth: {
redirect: {
login: '/login',
logout: '/login',
callback: false,
home: '/'
},
strategies: {
local: {
endpoints: {
login: { url: '/auth/sign_in', method: 'post', propertyName: false },
logout: false,
user: false
}
}
}
}
axios
baseURL
を設定する事で、HTTPリクエストを送信するベースとなるURLが自動で設定されます。
ログインする際のRailsアプリのURLは/auth/sign_in
のみですが、NuxtアプリでHTTPリクエストを送信する時に、axiosがhttp:localhost:3000/auth/sign_in
にURLを自動的に変えてくれます。これを設定しないとうまくいかないので、忘れない様にしましょう。
redirect
-
redirect
: ユーザの状態(ログインしているか否か)でどこにリダイレクトするかなどを設定します。 -
login
: ユーザが未ログイン時、ログインしていないとアクセスできないページにアクセスした際のリダイレクトURLです。 -
logout
: ユーザがログアウトした時のリダイレクトURLです。 -
callback
: Oauth認証等で必要となるコールバック用URLです。今回はOauth認証は使わないのでfalse
です。 -
home
: ログイン成功後にリダイレクトするURLです。
strategies
-
strategies
: Auth Moduleのどの認証ロジックを使うかを指定します。JWTとCookieを使うlocal
と、OAuthを使うsocial
の2種類があります。今回はJWTを使って認証を行うので、local
です。 -
endpoints
: どのメソッドが呼ばれた際にAPIのどのエンドポイントに飛ばすかを指定します。(logout
はログアウト用のAPIエンドポイントを指定しますが、今回は実装しないのでfalseです。同様にユーザ情報を取得するためのuser
もfalseにしています。)
ログイン画面の作成
次にログイン画面を作っていきます。(今回は、Vuetifyというマテリアルデザインフレームワークを使っているので、HTMLタグはVuetify仕様になっています。)
<template>
<div class="mt-3">
<v-card class="mt-5 mx-auto" max-width="600">
<v-form ref="form" v-model="valid" lazy-validation>
<v-container>
<v-row justify="center">
<p cols="12" class="mt-3 display-1 grey--text">
ログイン
</p>
</v-row>
<v-row justify="center">
<v-col cols="12" md="10" sm="10">
<v-text-field
v-model="email"
label="Eメールアドレス"
/>
<p class="caption mb-0" />
</v-col>
</v-row>
<v-row justify="center">
<v-col cols="12" md="10" sm="10">
<v-text-field
v-model="password"
type="password"
label="パスワード"
/>
</v-col>
</v-row>
<v-row justify="center">
<v-col cols="12" md="10" sm="10">
<v-btn
block
class="mr-4 blue white--text"
@click="loginWithAuthModule"
>
ログイン
</v-btn>
</v-col>
</v-row>
</v-container>
</v-form>
</v-card>
</div>
</template>
<script>
export default {
data () {
return {
password: '',
email: ''
}
},
methods: {
async loginWithAuthModule () {
await this.$auth.loginWith('local', {
data: {
email: this.email,
password: this.password
}
})
.then((response) => {
return response
},
(error) => {
return error
})
}
}
}
</script>
ここでAPIのUsersテーブルに保存してあるメールアドレスとパスワードを入力しログインボタンを押します。パスワードとメールアドレスは下記の様なJSONとしてAPIに送信されます。
{
"email": "momoko@test.xom",
"password": "password"
}
認証が通りAPIからHTTPレスポンスが返ってくると、ホーム(/
)ページにリダイレクトされるはずです。
ブラウザのディベロッパーズツールのコンソールで$nuxt.$store
を見てみます。ログイン後は$nuxt.$store.state.auth.loggedIn
がtrue
になっていることがわかります。
middleware
を設定する
middlewareを設定する事で、下記を自動化することができます。
- ログインしていなかったら、強制的にログイン画面へリダイレクトする
- ログインしていている状態でログイン画面にアクセスしようとするとホームへリダイレクトする
nuxt.config.js
でmiddlewareの設定を加えます。
router: {
middleware: ['auth']
}
これだけ!これでログインしている状態で/login
にアクセスしようとしてもホームにリダイレクトされるはずです。(サーバの再起動を忘れずに!)
ログインしてない時もこのページにはアクセスさせたい!という場合があると思います。そんなときは、そのページのVueファイルのscriptにauth: false
を入れてあげましょう。
<script>
export default {
auth: false,
}
</script>
Ruby on Railsのdevise_token_auth
でログイン実装・認証をする
ログイン・認証の流れ
Auth Moduleとdevise_token_auth
を使ってのログイン・認証の流れは下記です。
- 【ユーザ】 Nuxtアプリのログイン画面でEメールとパスワードを入力し、「送信」ボタンを押す
-
【Nuxtアプリ】 パスワードとEメール情報を付与し、Railsアプリの
/auth/sign_in
にHTTPリクエストを送信する -
【Nuxtアプリ】 認証されるとRailsアプリからレスポンスが返ってくる。レスポンスヘッダーの
accept-token
、uid
、client
を情報をVuex store
に保存する - 【Nuxtアプリ】 ユーザをログイン画面からホーム画面へリダイレクトする
-
【Nuxtアプリ】
axios
でHTTPリクエストを送る時にaccept-token
、uid
、client
をリクエストヘッダーに付与し認証済みであることをRailsアプリに教える
これが基本的な認証の流れです。
HTTPレスポンスのヘッダーにアクセスしたい・・・!
さて、ここから超ハマったパートです。Ruby on Railのdevise_token_authでユーザ認証をやりとりするには、ログイン成功後にRailsアプリから返ってくるHTTPレスポンスのaccess-token
、client
、uid
を取り出しVuex storeに保存、axiosでHTTPレスポンスを送信する度にこの3つをヘッダーに付与する必要があります。
この3つを付与せずにHTTPリクエストを送信する場合、Railsアプリは認証されていないと判断し、401 Unauthorized
のエラーを返します。
ということで、ログインが成功したらHTTPレスポンスのヘッダーにあるaccess-token
、client
、uid
にアクセスし、Vuex storeに保存してあげたい。でも、この3つにどうやってアクセスしたらええんや!?
・・・めちゃくちゃはまりました。
調べた結果、Auth ModuleのIssueに対応策が書いてありました。
どうやらAuth ModuleはlocalStorage(Vuex store)
にaccess-token
、client
、uid
などの認証情報を自動的に保存してくれる様です。そのため、ログイン成功時は、何もする必要はありません。
ただし、HTTPリクエストを送る時にlocalStorage
からこの3つのデータを取り出し、HTTPリクエストのヘッダーにセットする必要がありますよね。
ということで、plugins
ディレクトリにaxios.js
を作成し下記のコードを書いたところ、無事認証がされる様になりました!
// configの設定も忘れずに!
plugins: [
{ src: '~/plugins/axios.js', ssr: false }
]
export default function({ $axios }) {
$axios.onRequest(config => {
config.headers.client = window.localStorage.getItem("client")
config.headers["access-token"] = window.localStorage.getItem("access-token")
config.headers.uid = window.localStorage.getItem("uid")
config.headers["token-type"] = window.localStorage.getItem("token-type")
})
$axios.onResponse(response => {
if (response.headers.client) {
localStorage.setItem("access-token", response.headers["access-token"])
localStorage.setItem("client", response.headers.client)
localStorage.setItem("uid", response.headers.uid)
localStorage.setItem("token-type", response.headers["token-type"])
}
})
}
他にも方法があるかとは思いますが、今のところはこれが一番簡単そうです。別の方法知ってるよ〜という方がいらっしゃったらぜひ教えてください。