こちらは、現時点で最新のAdonisJS v5の記事になります。
前回データベースに接続できたので、次は認証機能を追加してユーザーをDBに保存できるようにする。
v4ではJWT認証で実装していたが、v5では代わってOpaqueAccessTokenを使っての認証になるので、実装方法を変える必要があったので、修正箇所も説明します。
公式リファレンス
https://docs.adonisjs.com/guides/auth/introduction
AdonisJS v5へのアップグレードで他にやったことはこちらにまとめています。
Authパッケージと設定の追加
設定追加時に認証方式をsessions, basic auth, API tokensから選ぶのだが、apiサーバーとして使ってるのでAPI tokensを使っていく。
$ yarn add @adonisjs/auth
$ node ace configure @adonisjs/auth
# いくつか入力を求められる
❯ Select provider for finding users · lucid
❯ Select which guard you need for authentication (select using space) · api
❯ Enter model name to be used for authentication · User
# すでにtableはあるからmigration fileは生成しない
❯ Create migration for the users table? (y/N) · false
❯ Select the provider for storing API tokens · database
# v4ではtokensテーブルだったので、api_tokensのmigration fileは生成する
❯ Create migration for the api_tokens table? (y/N) › true
CREATE: app/Models/User.ts
CREATE: database/migrations/1664350142101_api_tokens.ts
CREATE: contracts/auth.ts
CREATE: config/auth.ts
CREATE: app/Middleware/Auth.ts
CREATE: app/Middleware/SilentAuth.ts
UPDATE: tsconfig.json { types += "@adonisjs/auth" }
UPDATE: .adonisrc.json { providers += "@adonisjs/auth" }
CREATE: ace-manifest.json file
api_tokensテーブルを作成
トークン情報が保存されるapi_tokensテーブルを新たに作るためにマイグレーションを実行する。—dry-run
を付けてテスト実行してみたらエラーがでた。
$ node ace migration:run --dry-run
Exception
Migration completed, but unable to release database lock
adonis_schema_versionsテーブルがAdonis v4にはなかったためエラーになったようで、再度—dry-run
すると問題なかった。
$ node ace migration:run --dry-run
------------- database/migrations/1664350142101_api_tokens -------------
CREATE TABLE `api_tokens` (`id` INT unsigned NOT NULL auto_increment PRIMARY KEY, `user_id` INT unsigned, `name` VARCHAR(255) NOT NULL, `type` VARCHAR(255) NOT NULL, `token` VARCHAR(64) NOT NULL, `expires_at` TIMESTAMP NULL, `created_at` TIMESTAMP NOT NULL);
ALTER TABLE `api_tokens` ADD CONSTRAINT `api_tokens_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE;
ALTER TABLE `api_tokens` ADD UNIQUE `api_tokens_token_unique`(`token`)
------------- END -------------
マイグレーションを実行して、テーブル作成する。
$ node ace migration:run
❯ error database/migrations/1664350142101_api_tokens
[ error ] create table `api_tokens` (`id` int unsigned not null auto_increment primary key, `user_id` int unsigned, `name` varchar(255) not null, `type` varchar(255) not null, `token` varchar(64) not null, `expires_at` timestamp null, `created_at` timestamp not null) - Invalid default value for 'created_at'
エラーでた。。。
created_atのデフォルト値を指定する必要がありそうだ。
https://knexjs.org/guide/schema-builder.html#timestamp
- table.timestamp('created_at', { useTz: true }).notNullable()
+ table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(this.now())
再実行したら、作成できた。
$ node ace migration:run
❯ migrated database/migrations/1664350142101_api_tokens
Migrated in 296 ms
Social認証のためにallyパッケージと設定の追加
$ yarn add @adonisjs/ally
$ node ace configure @adonisjs/ally
各ソーシャルのログイン認証に必要な環境変数を追加する。
export default Env.rules({
// ### Variables for Twitter provider
TWITTER_CLIENT_ID: Env.schema.string(),
TWITTER_CLIENT_SECRET: Env.schema.string(),
// ### Variables for Facebook provider
FACEBOOK_CLIENT_ID: Env.schema.string(),
FACEBOOK_CLIENT_SECRET: Env.schema.string(),
})
TWITTER_CLIENT_ID=clientId
TWITTER_CLIENT_SECRET=clientSecret
FACEBOOK_CLIENT_ID=clientId
FACEBOOK_CLIENT_SECRET=clientSecret
Auth middlewareを登録
リクエストに対して、認証されているかを確認するためにAuth Middlewareを使用する。
Middlewareを以下のように設定すると、HTTPリクエストがルートハンドラに到達する前にMiddlewareに設定した内容が実行され、リクエストを終了させたり次の関数に転送したりする。
Route.get('mypage', 'MypageController.index').middleware(['auth'])
node ace configure @adonisjs/auth
実行時に、app/Middleware/Auth.ts
という認証チェックするものが作成されているので、それをauthというミドルウェアとして登録する。
Server.middleware.registerNamed({
auth: () => import('App/Middleware/Auth'),
})
ログインRouteを作成
ログイン認証するAPIを作成する。
例としてTwitterログインをできるようにしていく。/api/v1/user/twitter
に認証周りのAPIを作成していく。
Route.group(() => {
Route.get('user/login/twitter', 'UserController.loginTwitter')
Route.get('user/login/twitter/callback', 'UserController.loginTwitterCallback')
}).prefix('api/v1').namespace('App/Controllers/Http/Api/v1')
// namespaceがrootからのパスに変わってる。
// v4では、namespace('Api/v1')だった
処理はUserControllerに書いていく。
$ node ace make:controller Api/v1/UserController
CREATE: app/Controllers/Http/Api/v1/UserController.ts
export default class UsersController {
// ここにアクセスしてきたら、Twitter上の認証画面にリダイレクト
public async loginTwitter({ ally }: HttpContextContract) {
return ally.use('twitter').redirect()
}
// 認証画面からのコールバック
public async loginTwitterCallback({ ally, auth }: HttpContextContract) {
try {
const twitter = ally.use('twitter')
Logger.info(await twitter.user())
// 認証後の処理
// DBにユーザーを追加したり、トークンを発行したりして
// クライアント側に戻す
} catch (error) {
Logger.error(error)
return 'Unable to authenticate. Try again later'
}
}
}
http://127.0.0.1:3333/api/v1/user/login/twitter
にアクセスしてログインができるか確認しておく。
Callback用のViewを作っていく
自分の場合は、ログインボタン押下→別ウィンドウが開く→ログインが完了したらウィンドウを閉じる→元ウィンドウをマイページに遷移させる といった動作にしている。
なので、元ウィンドウにトークン情報を渡すために、Adonis側で一部Viewを用意している。
viewパッケージと設定の追加
$ yarn add @adonisjs/view
$ node ace configure @adonisjs/view
Callback用のViewテンプレートを生成
$ node ace make:view user/social-callback
CREATE: resources/views/user/social_callback.edge
social-callback.edge、ハイフンで指定していたが、Adonis v5ではsocial_callback.edge、アンダーバーで生成されることに注意
<p>Authenticated successfully.</p>
<script type="text/javascript">
// 元ウィンドウにトークンを送る
window.opener.postMessage({{{toJSON(token)}}}, '{{targetOrigin}}')
window.close();
</script>
UserControllerは以下のようにした。
public async loginTwitterCallback({ ally, auth, view }: HttpContextContract) {
try {
const twitter = ally.use('twitter')
if (twitter.accessDenied()) {
return 'Access was denied'
}
if (twitter.stateMisMatch()) {
return 'Request expired. Retry again'
}
if (twitter.hasError()) {
return twitter.getError()
}
const twUser = await twitter.user()
if (!twUser.email) {
return 'Cannot get email.'
}
// user details to be saved
const userDetails = {
email: twUser.email,
source: 'twitter',
}
const whereClause = {
email: twUser.email,
source: 'twitter',
}
const user = await User.firstOrCreate(whereClause, userDetails)
const token = await auth.use('api').generate(user, {
expiresIn: '14 days',
})
Logger.info(JSON.stringify(token))
view.share({
token,
targetOrigin: Env.get('URL'),
})
return view.render('user/social_callback')
} catch (error) {
Logger.error(error)
return 'Unable to authenticate. Try again later'
}
}
renderの書き方がv4と少し変わっていた。
viewを指定するときドットで繋いでいたのがDeprecatedされて、スラッシュ/で繫ぐようになった。
- return view.render('user.social-callback')
+ return view.render('user/social_callback')
以上にて、Adonisでログイン認証設定が完了。