20191021のlaravelに関する記事は13件です。

LaravelにVue.jsを導入したらバグった

練習用の学習プロジェクトを仕様変更しようと思い立ち、せっかくだから勉強中のVue.jsを導入してみようとやってみた。

・・初っ端からいきなりレンダリングがバグった。

スクリーンショット 2019-10-21 23.38.00.png

<input type = "number" id = "text_num{{$item->id}}" name="text_num{{$item->id}}" 
style="display:inline-block;width:30px;height:20px" min="1" max="9" value=1>

ちょっとこのブログだとワードラップするのでわかりにくいけど、
上のコードは真ん中で改行が入ってます。
結構長めのタグで画面を開発画面を大きくはみ出て読めないので改行いれてました。

今まで問題ありませんでしたが、なぜかbladeにvueを混ぜるとタグの間に改行入れると写真のように改行したところから下のコードがむき出しでレンダリングされました。
とりあえず改行して一行にしたら治りました。
何かエスケープコードかアノテーションみたいなのでキチンと直す方法があるのかもしれませんが今のところわかりません。

コメントのご指摘より追記

改行のせいじゃなくて、全角スペースが入っている事が根本的な原因だったようです。
失礼しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メモ

Laradock を使って nginx, Laravel, PHP, MySQL, phpMyAdmin のdocker環境を構築

  • ドキュメント 2.2 Installation A.2 の条件に書いてあるコマンドを実行してもmysqlのコンテナが落ちて動かない...
    • MySQLのバージョンが8以上だと MySQLサーバー側で使われる認証方式がLaravel側でサポートされていないため認証に失敗する
      • ~/laradock/.env の MYSQL_VERSION を latest から 5.7.28 に変更
    • パスの指定がデフォルトの状態だとファイルが生成されてない
      • ~/laradock/.env の DATA_PATH_HOST を ~/.laradock/data から .laradock/data に変更
  • ~/laradock/.env の設定内容
    • APP_CODE_PATH_HOST=../laravel/
    • DATA_PATH_HOST=.laradock/data
    • MYSQL_VERSION=5.7.28
    • MYSQL_DATABASE=laravel
    • MYSQL_USER=laravel
    • MYSQL_PASSWORD=laravel
    • MYSQL_PORT=3306
    • MYSQL_ROOT_PASSWORD=root
  • ディレクトリ

    project
    ├── laradock/ (<- git clone https://github.com/laradock/laradock.git)
    └── laravel/

ターミナルからlaradockディレクトリに移動して docker-compose up -d nginx mysql phpmyadmin workspace のコマンド(20分位かかる)を実行したあと docker-compose exec --user=laradock workspace bash コマンドで workspace に入って laravelをインストール composer config -g repositories.packagist composer https://packagist.jp && composer global require hirak/prestissimo && composer create-project laravel/laravel .

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AH01630: client denied by server configuration | Lazy Service

Lazy Serviceを使う際のエラー対処メモ1

事象

Windows 10環境でLaravelプロジェクトを開発しているが、Apache動作がうまくできず、WEB上にForbiddenエラー発生してしまう。
操作手順は下記通り。
1.Laravelプロジェクト(myproject)を新規作成する。
  Laravelプロジェクトの作成方法は「WindowsでのLaravel+SQLite3開発環境を構築する」を参照ください。
2.Lazy Serviceアプリを起動してDocumentRootに"C:\TEST\myproject\public"を指定した。
   image.png
3.APACHE -> SAVEでhttpd.confを保存する。
4.SERVER -> START でApache2.4を起動する。
5.ブラウザに "http://www.example.com" を入力し表示すると、下記のForbiddenメッセージが表示される。
image.png
一旦、Lazy ServiceのApacheログ(C:\Program Files\LazyService\Apache\2.4\logs\error.log)を覗いてみる。
下記エラーログがあった。

errors.log
[Mon Oct 21 21:10:37.589626 2019] [authz_core:error] [pid 14772:tid 1180] [client 192.168.x.x:57461] AH01630: client denied by server configuration: C:/TEST/myproject/public/

対応方法

APACHE -> Directory -> Section -> "Directory"を選択して、+ボタンをクリックする。
image.png

Direcotry設定子画面が表示され、以下のように各値を設定する

Options FollowSymlinks Includes
AllowOverride All
Require all granted

image.png
image.png
image.png

設定後、「OK」ボタンをクリックする。赤枠1行が追加された。それで"C:/TEST/myproject/public/"へのアクセス権限を付与した。
image.png
続いで、APACHE -> SAVEでhttpd.confを保存し、SERVER -> START でAPACHEを再起動する。
ブラウザに "http://www.example.com" で再度表示すると今回OK。
image.png

Lazy Serviceの良いことはhttpd.confなど設定ファイルを開く必要がなく、画面操作だけ開発環境が楽にできてくれる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelでDBの区分値をEnum化していい感じに扱う

ソフトウェアでテーブル内の区分値を扱う方法は様々あるかと思いますが、個人的に扱いやすくできたので備忘録です。
本稿における区分値とは以下のような各種区分をDB上で扱いやすくするために、数値化などをおこなった値を指しています。

性別区分
[
    'gender' => [
        0 => 'not known',
        1 => 'male',
        2 => 'female',
        9 => 'not applicable'
    ]
]
権限区分
[
    'roles' => [
        1 => 'developer',
        2 => 'administrator',
        3 => 'staff'
    ]
]

TL;DR

myclabs/php-enum を使用して、区分値をEnum化し、Model内でよしなにする。

Enumって何

Enumとは Enumerate の略で 列挙 を意味します。列挙型は、その名の通り定数を列挙したリストのようなものを作成することができ、区分値を扱う際によく利用されます。実装にもよりますが、値が同じでも異なるオブジェクトとして扱うことができるというメリットがあります。

例えば、前述の性別と権限の区分値を定数として実装した場合 MALE === DEVELOPER はどちらも値が 1 であるため true となり、バグを生み出してしまう可能性があります。

Enumは、言語によっては組み込まれている場合もありますが、PHPには組み込まれていない型であるため、PHPで使用したい場合は、自作するか外部ライブラリを使用する必要があります。

一般的に定数を扱うよりも処理が遅くなるので、定数はすべて列挙型というわけではなく、区分値などの適した場面で使用するのがよいかと思います。

区分値をEnum化する

Enumライブラリのインストール

PHPのEnumライブラリは複数ありますが、本稿ではGitHubのStar数が多い & 汎用的に扱えることから、myclabs/php-enumを使用します。

以下のコマンドでインストールします。

$ composer require myclabs/php-enum

Enumの作成

ファイルを作成する場所はお好みですが、今回は app 以下に Enums ディレクトリを作成し、そこに定義します。権限区分を例にEnumを作っていきます。

Role.php
<?php

namespace App\Enums;

use MyCLabs\Enum\Enum;

class Role extends Enum
{
    const DEVELOPER = 1;
    const ADMINISTRATOR = 2;
    const STAFF = 3;
}

Enumの作成はこれだけです。

モデルからRoleのEnum値を取得できるようにする

User のModel内に Userrole_code 属性のEnum値を取得するアクセサを定義します(LaravelのEloquentでは getHogeHogeAttribute などのメソッドを定義することで、 $user->hoge_hoge のように取得することができるようになります)。

まず、 User に先程作成したEnum Role をuseします。

今回採用したEnumライブラリはコンストラクタにEnumに設定した値を渡すことで、定義した値を持つEnum値を得ることができます。以下のように記述し、$user->role$user->role_code に対応するEnum値が取得できるようします。
(モデルのソースはuseなど色々省略しています)

User.php
<?php

namespace App\Models;

use App\Enums\Role;

class User
{
    public function getRoleAttribute()
    {
        return new Role($this->role_code);
    }

}

Enumを使ってモデルに区分値を設定できるようにする

Userにミューテターを定義し、Userモデルの role 属性に Role のEnum値を代入することで、同時に role_code にも対応する区分値が代入されるようにします。

setRoleAttribute
public function setRoleAttribute(Role $role)
{
    $this->role_code = $role->getValue();
}

Modelから権限を判定できるようにする

せっかくなので、 php-enum に備わっている equals を利用してModel内で権限も判定できるようにしておきます。

equalsに渡す引数はメソッド呼び出しの形式であることに注意します。 () をつけないとクラス変数という扱いになり、正しい比較ができません。 メソッド呼び出しの形式にすることで、 Enum型を返却してくれます。

User.php
<?php

namespace App\Models;

use App\Enums\Role;

class User
{
    public function getRoleAttribute()
    {
        return new Role($this->role_code);
    }

    public function setRoleAttribute(Role $role)
    {
        $this->role_code = $role->getValue();
    }

    public function getIsDeveloperAttribute()
    {
        return $this->role->equals(Role::Developer());
    }

    public function getIsAdministratorAttribute()
    {
        return $this->role->equals(Role::Administrator());
    }

    public function getIsStaffAttribute()
    {
        return $this->role->equals(Role::STAFF());
    }

}

これで権限を判定する場面で扱いやすくなりました。

Labelが欲しい

それぞれの権限名称をフロント側に表示したい場合などがあるかと思います。Laravelの機能を使ってスマートに取得できるようにします。

langファイルを作成する

Laravelには app/resources/lang 以下に配置したディレクトリから、渡されたキーに対応する文字列を返却してくれる __ というヘルパーメソッドがいます。 app/resources/lang 以下には locale名を関したディレクトリが存在し、configで設定されたlocaleと同じ名前を持つディレクトリから取得される仕組みになっています。

今回はlocaleを ja に設定していますので、app/resources/lang/ja 以下に my_app.php を作成し、各区分の名称を記述します(ファイル名はなんでもいいです)。

my_app.php
<?php

return [
    'developer'     => '開発者',
    'administrator' => '管理者',
    'staff'         => '担当者'
];

これでコード内で __('myapp.developer') といったようにコールすることで、 '開発者' が取得できます。また、言語別にファイルを用意すればそれぞれの言語で取得できます。

Enum内に getLabel メソッドを作成する

メソッド名は何でもいいです。マジックメソッド __toString をオーバーライドしても良いのですが、デフォルトの挙動を破壊するのがなんとなく怖いので、今回は新たにメソッドを定義します。

getLabel では lang に定義した、Enumに対応する文字列を取得できるようなロジックを記述します。
今回はEnumのKeyを小文字にするだけで、langに定義したKeyになることと、今後 Role が追加されたとしても同じように対応できるだろうという考えで、EnumインスタンスのKeyをstrtolowerで小文字にすることでlangのキーを生成しています。単純に小文字にするだけでは対応できない場合は Enumのキー => langのキー のような連想配列を作成して取得するのがよさそうですね。

Role.php
<?php

namespace App\Enums;

use MyCLabs\Enum\Enum;

class Role extends Enum
{
    const DEVELOPER = 1;
    const ADMINISTRATOR = 2;
    const STAFF = 3;

    public function getLabel() {
        return __('my_app.'.strtolower($this->getKey()));
    }
}

Model内に getRoleLabelAttribute メソッドを作成する

最後に以下のようなメソッドをモデル内に定義すれば、 $user->role_label のような形でLabelを取得できます。

getRoleLabelAttribute
public function getRoleLabelAttribute()
{
    return $this->role->getLabel();
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laradockでマイグレーション実行できるようにする

ファイル作成

ery@DESKTOP:/project/myapp$ php artisan make:migration db_table_name

.envの設定

  • project\myapp.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=default
DB_USERNAME=default
DB_PASSWORD=secret

MySQLをdockerで実行できるようにpluginを設定

root@efd99ab69d3e:/# mysql -uroot -p
mysql> SELECT user, host, plugin FROM mysql.user;
+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| default          | %         | caching_sha2_password |
| root             | %         | caching_sha2_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | caching_sha2_password |
+------------------+-----------+-----------------------+
6 rows in set (0.00 sec)

mysql>  ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';
mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root';
mysql> ALTER USER 'default'@'%' IDENTIFIED WITH mysql_native_password BY 'secret';

mysql> SELECT user, host, plugin FROM mysql.user;
+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| default          | %         | mysql_native_password |
| root             | %         | mysql_native_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | mysql_native_password |
+------------------+-----------+-----------------------+
6 rows in set (0.00 sec)

laradockのmysqlの設定

  • laradock/mysql/my.cnf に下記の通り追加
[mysqld]
sql-mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
character-set-server=utf8
default_authentication_plugin= mysql_native_password # <- 追加

アプリが動いているコンテナに入って実行。

  • ※laravelのドキュメントより、マイグレーションはコンテナ内に入って実行するとのこと。
ery@DESKTOP:/project/laradock$ docker exec -it laradock_workspace_1 bash
root@ery0606060:/var/www# php artisan migrate -v

実行後mysqlのコンテナ入ってテーブルを確認

ery@DESKTOP:/project/laradock$ docker exec -it laradock_mysql_1 bash
root@ery0606060:/# mysql -udefault -psecret default

mysql> show tables;
+-------------------------------------+
| Tables_in_default                   |
+-------------------------------------+
| db_table_name                       |
| failed_jobs                         | <- laravelデフォルトで必要なテーブル
| migrations                          |
+-------------------------------------+
3 rows in set (0.00 sec)

mysql> show create table db_table_name;
+-------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table                               | Create Table                                                                                                                                                                                                                                                                                                              |
+-------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| db_table_name | CREATE TABLE `db_table_name` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `hogehoge` int(11) NOT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
+-------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelのフォームリクエストで認可(authorize)が不可だった場合のエラーメッセージの変更方法

Laravelのフォームリクエストのauthorizeメソッドがfalseだった場合に返されるエラーメッセージを日本語にしたかったので方法を調べました。

フォームリクエストクラス内でfailedAuthorizationメソッドをオーバーライドして、AuthorizationExceptionにメッセージを引き渡してやれば、メッセージを任意のテキストできました。

    protected function failedAuthorization()
    {
        throw new \Illuminate\Auth\Access\AuthorizationException('この操作は許可されていません。');
    }

ちなみ、FormRequestクラスでは以下のように定義されており、デフォルトだとここで指定されているメッセージが返されるようです。

vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php
  /**
     * Handle a failed authorization attempt.
     *
     * @return void
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    protected function failedAuthorization()
    {
        throw new AuthorizationException('This action is unauthorized.');
    }

より適切なエラーメッセージの変更方法があれば、コメントいただければ幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel tymon/jwt-auth による JWT 認証

環境

  • Laravel 6.0
  • tymon/jwt-auth 1.0.0-rc.5 (Laravel 6 には、1.0.0-rc.5 以降が必要)

インストール手順

参考) jwt-auth > Docs > Laravel Installation

インストール

$ composer require tymon/jwt-auth:1.0.0-rc.5

設定ファイルを publish

設定を変更しない場合は不要。

$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

シークレット作成

php artisan jwt:secret

.env に秘密鍵 JWT_SECRET が追記される。

User モデルに JWTSubject を実装

参考) jwt-auth > Docs > Quick Start

Tymon\JWTAuth\Contracts\JWTSubject を実装する。

app/User.php

<?php
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    ...

    public function getJWTIdentifier()
    {
        // JWT トークンに保存する ID を返す
        return $this->getKey();
    }

    public function getJWTCustomClaims()
    {
        // JWT トークンに埋め込む追加の情報を返す
        return [];
    }
}

JWTGuard を使うように設定

参考) Laravel > Authentication > Adding Custom Guards

config/auth.php

<?php
'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],
...
'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

認証エンドポイントの実装

まずは公式ドキュメントの Quick start にある AuthController をそのまま実装して使ってみる。

認証API

  • POST /api/auth/login : ログイン
  • POST /api/auth/logout : ログアウト
  • POST /api/auth/refresh : トークンをリフレッシュ
  • POST /api/auth/me : ユーザー情報取得

認証

ログインAPIにユーザー名/パスワードを渡し、アクセストークンを取得する。

$ curl -X POST -H "Accept: application/json" \
    -F "email=test@example.jp" \
    -F "password=password" \
    http://localhost/api/auth/login | jq
{
  "access_token": "(JWTトークン)",
  "token_type": "bearer",
  "expires_in": 3600
}

認証エラー時

$ curl -X POST -H "Accept: application/json" \
    -F "email=test@example.jp" \
    -F "password=incorrect" \
    http://localhost/api/auth/login | jq
{
  "message": "Unauthenticated."
}

ユーザー情報取得

認証つきAPI要求時は Authorization ヘッダにアクセストークンを付与する。

$ curl -X POST -H "Accept: application/json" \
    -H "Authorization: Bearer (JWTトークン)" \
    http://localhost/api/auth/me | jq
{
  "id": 1,
  "name": "Test User",
  "email": "test@example.jp",
  "email_verified_at": null,
  "created_at": "2019-05-10 06:51:16",
  "updated_at": "2019-05-10 06:51:16"
}

リフレッシュ

アクセストークンには有効期限があるので、それを超えて使う場合は新しいトークンを取得する必要がある。再度認証をおこなわずに新しいトークンに更新するには、リフレッシュAPIを使用する。

$ curl -X POST -H "Accept: application/json" \
    -H "Authorization: Bearer (古いJWTトークン)" \
    http://localhost/api/auth/refresh | jq
{
  "access_token": "(新しいJWTトークン)",
  "token_type": "bearer",
  "expires_in": 3600
}
$ curl -X POST -H "Accept: application/json" \
    -H "Authorization: Bearer (古いJWTトークン)" \
    http://localhost/api/auth/refresh | jq
{
  "message": "Unauthenticated."
}

デフォルトの有効期限は発行から1時間、リフレッシュ有効期限は発行から2週間(この期限内であれば無効になったトークンを使って新しいトークンを取得することができる)。有効期限を変更する場合は以下の設定を .env に追記する。

設定 デフォルト値 説明
JWT_TTL 60 (1時間) トークンの有効期限 (分)
JWT_REFRESH_TTL 20160 (2週間) トークンのリフレッシュ有効期限 (分)

リフレッシュをおこなうとすぐに古いトークンは使えなくなる。リフレッシュと並列に古いトークンでアクセスをおこなうと更新直後に認証が通らなくなってしまう。古いトークンをすぐに失効させたくない場合は、猶予期間を .env に設定する。

設定 デフォルト値 説明
JWT_BLACKLIST_GRACE_PERIOD 0 (無効) ブラックリスト猶予期間 (秒)

ログアウト

ログアウトするとそのアクセストークンは使えなくなる。jwt-auth では発行したトークンを管理しているわけではなく、逆に無効化したトークンをキャッシュに保持して拒否する実装となっているため、キャッシュをクリアすると再度使用できるようになるので注意。リフレッシュで無効になったトークンも同様。

$ curl -X POST -H "Accept: application/json" \
    -H "Authorization: Bearer (JWTトークン)" \
    http://localhost/api/auth/logout
{"message":"Successfully logged out"}
$ curl -X POST -H "Accept: application/json" \
    -H "Authorization: Bearer (JWTトークン)" \
    http://localhost/api/auth/me
{"message":"Unauthenticated."}

セキュリティの確認

JWTトークン

JWTトークンはユーザーIDを含む JSON をエンコードして署名をつけただけのもの((ヘッダー).(ペイロード).(署名)という構成)。サーバー側に設定された秘密鍵を持たないと改竄はできないが、中身を知ることはできる。

受信したJWTトークンをデコードしてみる。

$ echo "(JWTトークンのヘッダー)" | base64 --decode - | jq
{
  "typ": "JWT",
  "alg": "HS256"
}
$ echo -n "(JWTトークンのペイロード)" | base64 --decode - | jq
{
  "iss": "http://localhost/api/auth/login",
  "iat": 1557471077,
  "exp": 1557474677,
  "nbf": 1557471077,
  "jti": "qyDgabCpUk6atauK",
  "sub": 9,
  "prv": "87e0af1ef9fd15812fdec97153a14e0b047546aa"
}

"sub" がユーザーIDをあらわしている。

署名なしのJWTトークン

JWTトークンの署名アルゴリズムは選択することができ、"none" (署名なし) もJWTトークンとしては Valid である。

署名アルゴリズムに "none" を指定して、JWT トークンをねつ造してみる。

$ echo -n '{"typ":"JWT","alg":"none"}' | base64
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0=
$ echo -n '{"iss":"http:\/\/localhost:8000\/api\/auth\/login","iat":1557471077,"exp":1557474677,"nbf":1557471077,"jti":"qyDgabCpUk6atauK","sub":1,"prv":"87e0af1ef9fd15812fdec97153a14e0b047546aa"}' | base64
eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODAwMFwvYXBpXC9hdXRoXC9sb2dpbiIsImlhdCI6MTU1NzQ3MTA3NywiZXhwIjoxNTU3NDc0Njc3LCJuYmYiOjE1NTc0NzEwNzcsImp0aSI6InF5RGdhYkNwVWs2YXRhdUsiLCJzdWIiOjEsInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEi

このJWTトークンで認証してみる。

$ curl -X POST -H "Accept: application/json" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODAwMFwvYXBpXC9hdXRoXC9sb2dpbiIsImlhdCI6MTU1NzQ3MTA3NywiZXhwIjoxNTU3NDc0Njc3LCJuYmYiOjE1NTc0NzEwNzcsImp0aSI6InF5RGdhYkNwVWs2YXRhdUsiLCJzdWIiOjEsInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEifQ." http://localhost/api/auth/me
{"message":"Unauthenticated."}

大丈夫っぽい。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel: Vessel によるローカル開発用コンテナ環境を使う

環境

概要

Laravel のローカル開発環境を Docker Compose で動作させるための設定とショートカットコマンドを提供してくれる。

導入

インストール

$ composer require --dev shipping-docker/vessel

設定の取り込み

$ php artisan vendor:publish --provider="Vessel\VesselServiceProvider"

これにより以下が生成される。

  • docker/ 配下 ... イメージ作成のための設定
    • app/
    • mysql/
    • node/
  • docker-compose.yml
  • vessel ... コマンド

docker/ 配下はファイルがいろいろあるが、これをベースに直接カスタマイズしていくのでコミットしてしまって良いと思う。

初期化

$ bash vessel init

このコマンドでおこなわれること。

  • composer require predis/predis

    • キャッシュとセッションに Redis を使うためにインストール
  • .env の設定変更

  DB_HOST=mysql
  CACHE_DRIVER=redis
  SESSION_DRIVER=redis
  REDIS_HOST=redis
  • データベースのホストを mysql コンテナに向ける設定
  • キャッシュとセッションの保存先を Redis に向ける設定
  • ※.env.example は変更されないので、必要があれば転記してコミットする

    • vessel コマンドへの実行権限追加

例)

起動

$ ./vessel start

docker-compose up -d と同じ。デフォルトで以下のコンテナが起動する。

  • app : Ubuntu 18.04 ベース。supervisord (nginx + php-fpm)
  • mysql:5.7
  • redis:alpine

データベース名、ユーザー等は .env から取得して初回起動時に作成される。DB_USERNAME に root を指定するとユーザーを作成しようとしてエラーになるので別のものにする。

他に必要なものがあれば docker-compose.yml を編集する。

停止

$ ./vessel stop

docker-compose down と同じ。

コンテナイメージの再ビルド

Dockerfile は docker/ フォルダ配下にあるので、必要に応じて編集し、再ビルドする。

$ ./vessel build

docker-compose up -d と同じ。

ログイン

$ ./vessel exec app bash

docker-compose exec app bash と同じ。

composer コマンド実行

$ ./vessel composer (コマンド)

app コンテナで composer を実行するショートカット。

artisan コマンド実行

$ ./vessel artisan (コマンド)

app コンテナで php artisan を実行するショートカット。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel tymon/jwt-auth でトークンを Cookie に保存する

環境

概要

jwt-auth では、JWT トークンを以下の順番で検索する。

  • Authorization ヘッダ (Bearer)
  • Request->query('token')
  • Request->input('token')
  • Route パラメタ (token)
  • Cookie (token)

ということで、トークンを発行したら、token という名前の Cookie を設定してあげればよい。

設定手順

Cookie 設定例

class AuthController extends Controller
{
    public function login(Request $request, CookieJar $cookie): Response
    {
        $credentials = request(['email', 'password']);
        $token = auth()->attempt($credentials);
        ...
        return response(...)
            ->withCookie($cookie->make(
                'token',
                $token,
                config('jwt.refresh_ttl'), // minutes
                null, // path
                null, // domain
                $request->getScheme() === 'https', // secure
                true // httpOnly
            ));
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel での tymon/jwt-auth による JWT トークンの自動更新

環境

概要

JWT トークンはトークン自体に有効期限が埋め込まれており、期限を更新するにはトークン自体を置き換えなければならない。
フロントで有効期限を管理してトークンのリフレッシュをせずに、スライディングセッションを実現したい。

トークンは Cookie でフロントに保存するものとする。サーバー側で期限切れを検出したら新しいトークンを Cookie に設定してあげる。

トークンの有効期限(JWT_TTL:デフォルト1時間)が切れても、リフレッシュ有効期限(JWT_REFRESH_TTL:デフォルト2週間)内であればそのトークンを用いて新しいトークンを発行することができる。

ログインシーケンス

トークン更新シーケンス

実装

サーバー側でトークンの有効期限切れを検出し、リフレッシュをおこなって返すミドルウェアを作成する。不正なトークンや更新に失敗したりした場合はこのあとの通常処理で 401 になるので、何もしない。

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class RefreshToken extends BaseMiddleware
{
    public function handle($request, Closure $next)
    {
        $token = $newToken = null;
        try {
            $token = $this->auth->parseToken();
            $token->authenticate();
        } catch (TokenExpiredException $e) {
            // Token expired: try refresh
            try {
                $newToken = $token->refresh();
            } catch (JWTException $e) {
                // Refresh failed (refresh expired)
            }
        } catch (JWTException $e) {
            // Invalid token
        }

        $response = $next($request);
        if ($newToken) {
            // Send the refreshed token back to the client.
            $response->withCookie(cookie(
                'token',
                $newToken,
                config('jwt.refresh_ttl'), // minutes
                null, // path
                null, // domain
                $request->getScheme() === 'https', // secure
                true // httpOnly
            ));
        }
        return $response;
    }
}

このミドルウェアを有効にする。

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    ...
    protected $middlewareGroups = [
        'api' => [
            ...
            \App\Http\Middleware\RefreshToken::class, // JWTトークン更新 ※bindings の前に置く
            'bindings',
            ...
        ],
    ];
}

ブラックリスト猶予期間を設定

リフレッシュがおこなわれると、即時古いトークンは使えなくなるため、フロントから同時に複数のリクエストをおこなった場合、一部のリクエストが失敗してしまう可能性がある。これを防止するため、一定時間は古いトークンも使用できるようにする。.env にブラックリスト猶予期間(JWT_BLACKLIST_GRACE_PERIOD)を設定しておく。(例: 15秒)

JWT_BLACKLIST_GRACE_PERIOD=15
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel: 開発時だけ他ホストからの要求を許可する (CORS)

背景

開発時のみフロントエンドを別ホスト・ポートで動作し、クロスサイトでのリクエストを許可したい。本番には適用しない。

実装

CORS ヘッダを設定するミドルウェアを作成する。

クロスサイトで認証情報つき(withCredentials)リクエストをおこないたい場合は Access-Control-Allow-Origin: * が許可されないため、リクエスト元のホスト名を入れて返している。
参考) Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class Cors
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        if (env('APP_DEBUG') && $request->header('Origin')) {
            $response
                ->header('Access-Control-Allow-Credentials', 'true')
                ->header('Access-Control-Allow-Origin', $request->header('Origin'))
                ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
                ->header('Access-Control-Allow-Headers', 'Origin, Authorization, Accept, Content-Type, X-XSRF-Token')
                ->header('Access-Control-Expose-Headers', 'Authorization');
        }

        return $response;
    }
}

このミドルウェアを有効にする。

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    ...
    protected $middleware = [
        ...
        \App\Http\Middleware\Cors::class
    ];
    ...
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel tymon/jwt-auth のブラックリストをデータベースに保存する

Laravel/Lumen 用の JWT 認証パッケージである tymon/jwt-auth では、発行した有効なトークンを保持するのではなく、ログアウトしたりリフレッシュされた古いトークンをブラックリストという形で保存しておく。デフォルトではキャッシュに保存されるので、Redis などで共有していないとスケールアウトできない。ということで、データベースに保存してみる。

環境

手順

テーブル jwt_auth_storage を作成

database/migrations 配下に定義してマイグレーションする。

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateJwtAuthStorageTable extends Migration
{
    public function up(): void
    {
        Schema::create('jwt_auth_storage', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->timestamps();
            $table->timestamp('expires_at')->nullable();
            $table->string('key')->index();
            $table->string('value');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('jwt_auth_storage');
    }
}

jwt_auth_storage テーブルの Eloquent Model を作成

<?php
namespace App\Model;

use Illuminate\Database\Eloquent\Model;

class JwtAuthStorage extends Model
{
    protected $table = 'jwt_auth_storage';

    protected $fillable = [
        'key', 'value', 'expires_at',
    ];

    protected $dates = ['expires_at'];
}

Storage を継承したアクセスクラスを作成

Tymon\JWTAuth\Contracts\Providers\Storage を継承し、各メソッドを実装したクラスを作成する。デフォルトでは Tymon\JWTAuth\Providers\Storage\Illuminate::class が使用されるので、これを参考にする。

<?php
namespace App\Repository;

use App\Model\JwtAuthStorage;
use DateTime;
use Tymon\JWTAuth\Contracts\Providers\Storage;

class JwtAuthStorageRepository implements Storage
{
    protected $jwtAuthStorageModel;

    public function __construct(JwtAuthStorage $jwtAuthStorageModel)
    {
        $this->jwtAuthStorageModel = $jwtAuthStorageModel;
    }

    public function add($key, $value, $minutes): void
    {
        $expiresAt = (new DateTime('now'))->modify('+ ' . $minutes . ' minutes');
        $this->jwtAuthStorageModel->newQuery()
            ->create([
                'key' => $key,
                'value' => serialize($value),
                'expires_at' => $expiresAt,
            ]);
    }

    public function forever($key, $value): void
    {
        $this->jwtAuthStorageModel->newQuery()
            ->create([
                'key' => $key,
                'value' => serialize($value),
            ]);
    }

    public function get($key)
    {
        $now = new DateTime('now');
        $data = $this->jwtAuthStorageModel->newQuery()
            ->where('key', $key)
            ->where('expires_at', '>', $now)
            ->orderBy('expires_at', 'desc')
            ->first();
        if ($data) {
            return unserialize($data->value);
        } else {
            return null;
        }
    }

    public function destroy($key): bool
    {
        return !!$this->jwtAuthStorageModel->newQuery()
            ->where('key', $key)
            ->delete();
    }

    public function flush()
    {
    }
}

設定ファイルを publish

設定を変更するため、config/jwt.php をプロジェクトに展開する。

$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

設定

設定ファイル(config/jwt.php)の provider.storage に、カスタマイズしたアクセスクラスを指定する。ブラックリストの読み書きはこのクラスを経由しておこなわれる。

<?php
return [
    ...
    'providers' => [
        ...
        'storage' => App\Repository\JwtAuthStorageRepository::class,
    ],
];
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelのプロジェクトをGithubで管理する流れ[自学習メモ]

はじめに

Github初心者がGithubでプロジェクトを管理をする方法を自分用にまとめたものです。
もし、同じような初学者の参考になれば幸いです。

環境(Gitバージョン)

$ git --version
git version 2.21.0 (Apple Git-122)

作業の流れ

この記事で行う作業の流れは、
Githubでリモートリポジトリを作成→ローカルにクローンを作成→Laravelプロジェクト作成→ Github上にプッシュ
となります。

Githubでリポジトリを作成する

「New repository」からリポジトリを作成します。この時、「Initialize this repository with a README」にチェックしましょう。
※ここでチェックし忘れても、githubのリポジトリのページに記載されている下記のようなコマンドを、次の作業で作成するクローンフォルダ内に移動した上で実行することでREADMEファイルを作成することができます。

echo "# プロジェクト名" >> README.md
user-MBP:プロジェクト名 username$ git init
Reinitialized existing Git repository in /Users/username/Desktop/プロジェクト名/.git/
user-MBP:プロジェクト名 iusername$ git add README.md
user-MBP:プロジェクト名 username$ git commit -m "first commit"
//このコマンドは参考用です。そのままターミナルに貼り付けても意味はありません。

スクリーンショット 2019-10-21 0.06.20.png

ローカルにリポジトリをクローンする

ローカル(自分のPC)にGithubで作成したリモートリポジトリ のクローンを作成します。

クローンはデスクトップ上にworkという作業用フォルダを作成し、その中に作成します。今後、何かしらのプロジェクトをGithubで管理する際はこのworkフォルダ内で作業を行うようにします。

まず、workフォルダを作成します。

$ cd Desktop //userからDesktopディレクトリに移動します。
$ mkdir work //workという名前の空のフォルダがデスクトップ上に作成されます。
$ ls //Desktopディレクトリ上のファイルを一覧できます。workフォルダが作成されたことを確認しましょう。
$ cd work //workフォルダ内に移動します。

スクリーンショット 2019-10-21 0.27.48.png

次に、GithubのリポジトリのWeb URLをコピーします。URLは上記の画像のように、「Clone or download」をクリックした先に表示されるClone with HTTPSのURLです。このURLを介してローカル上にリポジトリがクローンされます。

$ git clone <リポジトリのURL> //workフォルダ内にリモートリポジトリがクローンされます。
$ ls //クローンされて、ローカルリポジトリが作成されているかを確認しましょう。

Laravelのプロジェクトを作成する

次に作業を行うLaravelプロジェクトを作成します。

$ cd <ローカルリポジトリのフォルダ名> //フォルダを移動します。
$ laravel new <プロジェクト名> //ローカルリポジトリ内にLaravelプロジェクトが作成されます。
$ls //プロジェクトが作成されているか確認しましょう。

基本的にLaravelプロジェクトはクローンフォルダ内で作成したほうが良いようです。もし、すでに作成したLaravelプロジェクトを管理したい場合はフォルダごと全てをクローン内に移しましょう。

addからpushまでの流れ

次にLaravelプロジェクトをgithubで管理します。今回は作成したばかりのLaravelプロジェクトをGithubにpushします。

$ git branch //現在、masterブランチにいることを確認します。

masterブランチにいれば、masterという文字が緑色で表示されます。

特に何も表示されない場合は自分がいるディレクトリの位置を確認する、masterブランチが作成されているか確認するなどの確認をしましょう。
※もしみつからない場合は、先ほどご説明したように、READMEファイルの作成を行ってみてください(READMEを作り忘れた場合)。

$ git add . //ローカルリポジトリ内のファイルをインデックス登録してコミットする準備を行う。
$ git status //コミットの準備がされているファイル一式を確認することができます。

「git add .」はカレントディレクトリ(作業中のディレクトリ)内の変更があった全てのフォルダがaddされるコマンドです。

今回は、初めてLaravelプロジェクトをaddしたので、Laravelプロジェクトのファイルが全てaddされます。今後、Laravelプロジェクト内のファイルやフォルダ情報を変更した場合は、変更したファイルやフォルダのみがaddされます。

初めて「git add .」コマンドを入力すると無反応なので心配になりますが、「git status」コマンドを入力することでgit addが実行されていることを確認できます。このように初めは一つの作業ごとに確認を行ったほうが安心して作業できます。

$ git commit -m "コミットメッセージ" //addしたファイル一式がcommitされます。
$ git log //commit情報を確認できます。
$ git push origin master ////masterブランチにpushします。

以上です。Githubのリモートリポジトリを確認するとpushされたファイル一式が確認できると思います。

最後に

自分用のメモではありますが、初めてのQiitaへの記事投稿となります。
もし、ここ間違えているよ!、ここはこうしたほうがいいよ!という意見がありましたら、教えていただけると勉強になりますのでよろしくお願いします!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む