5
5

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.

【スマレジアプリを作ってみた#3】認証機能の実装

Posted at

さて、スマレジのデータを利用したアプリの開発について、
第3回目はスマレジ引換券モニターアプリの認証機能の実装についてお届けしたいと思います!
アプリ開発の中でも特につまづきやすいところかと思います!
注意点などもまとめておりますでぜひ参考にしてみてください!

※過去記事はこちら
第1回【スマレジアプリを作ってみた#1】アプリの登録とPostman設定
第2回【スマレジアプリを作ってみた#1】引換券モニターアプリの仕様と構成

データベース構造

まず最初に、認証機能の実装に必要なデータベース構造について見ていきましょう。
ER図のように、ユーザー情報とログイン情報を保存するためのauth_usersテーブルと 、
スマレジ契約情報を保存するためのsmaregi_contractsテーブルがあります。
smaregi_contractsauth_usersは1対多のリレーションシップとなります。

Database Design For Queue Monitor.png

認証フロー

スマレジAPIを介してアプリがユーザーのデータにアクセスするには、
アプリアクセストークンを取得する必要があります。
この記事全体を通して、アクセストークンを取得する方法と、
アクセストークンを取得するために必要な手順等について説明します。

まずは次の図で、このアプリに関する認証フローの概要を把握しましょう。
Flowchart Qmonitor Authentication.png

1. 認可リクエスト

OAuth 2.0 Authorization Code Grant と OpenID Connect に基づいて、
スマレジアカウントでのログインを利用できます。
ユーザーが初めてアプリを開く際は、認可エンドポイントURLに
クエリパラメータを含めてユーザーをリダイレクトします。
ユーザーは認証ページでスマレジアカウントを使ってアプリにアクセスを許可します。

認可エンドポイントURL

https://id.smaregi.dev/authorize
//ユーザーをスマレジ認可エンドポイントURLにリダイレクトする
return redirect()->to(authorizeUser());

public function authorizeUser(): string
{
    return "https://id.smaregi.dev/authorize?response_type=code&client_id={your_client_id}}&scope=openid+email+offline_access";
}

authorize()関数は
https://id.smaregi.dev/authorize?response_type=code&client_id= {your_client_id}}&scope = openid + email + offline_access
を返します。
このURLにリダイレクトした後、ユーザーはスマレジにログインします。

Screenshot from 2020-12-16 15-04-03.png

ログイン後、次の認証ページが表示されます。

認証ページ.jpg

パラメーター 値の説明
response_type code
client_id アプリのクライアントID
scope ユーザーに認可を要求するスコープ。複数の場合は半角スペースで結合して指定。

スコープ定義

スコープは、アプリによるユーザーデータへのアクセスを制限するために使用されます。
このアプリでは、OpenIDConnectで指定された

  • openid
  • email
  • offline_access

のスコープを使用して、APIプラットフォームでアクセスを取得します。

スコープ 説明
openid ログインユーザーの識別子の取得、UserInfoエンドポイントへのアクセス。
email ログインユーザーのアカウントのメールアドレスを参照。openidの指定が必須。
offline_access リフレッシュトークンの取得。

スマレジはクレデンシャルを確認します。
ユーザーが有効な場合、authorization_code
スマレジアプリで定義されたリダイレクトURIを介してcodeとしてアプリに送信されます。

環境設定-Qmonitor-アプリ-スマレジ・デベロッパーズ.png

2.authorization_codeを使ってアクセストークンをリクエストする

authorization_codeを受信した後、
アプリは再度スマレジにユーザーアクセストークンの取得をリクエストします。

ユーザーアクセストークンエンドポイントURL

https://id.smaregi.dev/authorize/token

前のステップで受け取った authorization_codeを使用してアクセストークンを取得しましょう。

[
    'access_token'  => $accessToken,
] = $this->authenticate($authorizationCode); // 前のステップで取得した認可コード


public function authenticate(string $authorizationCode): array
{
    $url          = "https://id.smaregi.dev/authorize/token";
    $clientId     = {client_id};//アプリのclient_id
    $clientSecret = {client_secret}; //アプリのclient_secret

    $option = new HttpRequestOption();
    $option->url($url);
    $option->method('POST');
    $option->token(sprintf("Basic %s", base64_encode("$clientId:$clientSecret")));

    $formData               = [];
    $formData['grant_type'] = 'authorization_code';
    $formData['code']       = $authorizationCode;
    $formData['scope']      = 'pos.transactions:write pos.stores:read pos.products:read';

    $option->formData($formData);
    return $this->request($option);
}

grant_typeauthorization_codeになっているかを確認してください。
client_idclient_secretをコロン(:)で結合して
 Base64エンコードしたものを指定しているかを確認してください。

レスポンス例

{
    "token_type": "Bearer",
    "expires_in": 3600,
    "access_token": "xNjA3NTc2MDIzLCJuYmYiOjE2MDc1NzYwMj...",
    "scope": "pos.transactions:write pos.stores:read pos.products:read"
}

3. アクセストークンを使用してユーザー情報を取得する

前のステップで取得したアクセストークンを使って、次のエンドポイントを介して
ユーザーのcontract_idおよびその他のユーザー情報を要求します。

UserInfoエンドポイントURL

https://id.smaregi.dev/userinfo

スマレジユーザー情報と契約情報を取得する


public function getUserInfo(string $accessToken): array
{
    $url    = "https://id.smaregi.dev/userinfo";
    $option = new HttpRequestOption();
    $option->url($url);
    $option->token($accessToken);
    return $this->request($option);
}
[   'sub'      => $smaregiId,
    'email'    => $smaregiEmail,
    'contract' => $smaregiContract,
] = $this->getUserInfo($accessToken);

contract_idを取得する

$contractId = Arr::get($smaregiContract, 'id');

レスポンス例

{
    "sub": "smaregi:qmonitorX1Y3Z",
    "email": "user@example.com",
    "contract": {
        "id": "sb_12a432",
        "is_owner": true
    }
}

4.contract_idを使ってアプリアクセストークンを取得する

上記のステップで取得したcontract_idを使って、アプリアクセストークンを取得します。
そのために、client_id client_secret、およびその他の必要なスコープを提供する必要があります。

アプリアクセストークンエンドポイントURL

https://id.smaregi.dev/app/{contract_id}/token
['access_token' => $appAccessToken] = $this->appAccessToken($contractId);


public function appAccessToken(string $contractId): array
{
    $url          = "https://id.smaregi.dev/app/{contract_id}/token"; // 前のステップで取得したcontract_id
    $clientId     = {client_id}; //アプリのclient_id
    $clientSecret = {client_secret}; //アプリのclient_secret

    $option = new HttpRequestOption();
    $option->url($url);
    $option->method('POST');
    $option->token(sprintf("Basic %s", base64_encode("$clientId:$clientSecret")));

    $formData               = [];
    $formData['grant_type'] = 'client_credentials';
    $formData['scope']      = 'pos.transactions:write pos.transactions:read pos.stores:read pos.products:read';

    $option->formData($formData);
    return $this->request($option);
}

grant_typeclient_credentialsになっているかを確認してください。
client_idclient_secretをコロン(:)で結合して
 Base64エンコードしたものを指定しているかを確認してください。

このトークンを使って、アプリがユーザーの代わりにスマレジシステムにアクセスすることができます。

レスポンス例

{
    "scope": "pos.transactions:write pos.stores:read pos.products:read",
    "token_type": "Bearer",
    "expires_in": 3600,
    "access_token": "FrGbxdfNjA3aNTc2MDIzLCJuYmYiOjE2MDc1NzYwMj..."
}

5. ユーザー情報を保存してアプリにログインする

最後に、ユーザー情報をアプリに保存・登録します。
このプロセスでは、ユーザーの契約情報も保存します。

スマレジ契約情報を保存する

public function saveContract(string $contractId, string $appAccessToken): SmaregiContract
{
    return SmaregiContract::updateOrCreate(
        ['contract_id' => $contractId],
        [
            'smaregi_system_access_token' => $appAccessToken,
        ]
    );
}
$contract = $this->saveContract($contractId, $appAccessToken);

ユーザー情報を保存してログインする

public function login(SmaregiContract $smaregiContract, $userData)
{
    $user = $smaregiContract->users()->updateOrCreate(
        [
            'smaregi_contract_id' => $smaregiContract->id,
            'email'       => $userData->email,
        ],
        [
            'smaregi_id'            => $userData->smaregiId,
            'email'                 => $userData->email,
            'smaregi_access_token'  => $userData->smaregiAccessToken,
            'smaregi_refresh_token' => $userData->smaregiRefreshToken,
            'logged_in_at'          => Carbon::now(),
        ]
    );
    auth()->login($user, true);
}
$userData = [
    'smaregiId'           => $smaregiId,
    'smaregiEmail'        => $smaregiEmail,
    'smaregiAccessToken'  => $accessToken,
    'smaregiRefreshToken' => $refreshToken,
]

$this->login($contract, $userData);

店舗情報を取得する
アクセストークンを使って店舗情報を取得するため、次のエンドポイントにリクエストを送信します。
https://api.smaregi.dev/{contract_id}/pos/stores/
詳細はスマレジ・プラットフォームAPI POS仕様書の店舗一覧取得を確認してください。

$stores = getStores($appAccessToken, $contract->contract_id);
public function getStores(string $accessToken, string $contractId, array $query = []): array
{
    $url    = "https://api.smaregi.dev/{$contractId}/pos/stores/";

    $option = new HttpRequestOption();
    $option->url($url);
    $option->token("Bearer $accessToken");
    $option->query($query);

    return $this->request($option);
}

それから、取得した契約IDに紐づく店舗情報をアプリに保存します。

$this->saveStores($contract, $stores);
public function saveStores(SmaregiContract $contract, $store)
{
    return $contract->smaregi_stores()->updateOrCreate(
        [
            'smaregi_contract_id'      => $contract->id,
            'smaregi_store_id'         => $store->storeId,
        ],
        [
            'smaregi_store_id'         => $store->storeId,
            'smaregi_store_name'       => $store->storeName,
            'is_paused'                => $store->isPaused,
        ]
    );
}

注意点

上記のコードを見るとわかるように、店舗情報を取得するには
アクセストークンとcontract_idを使用しますが、
このアプリに保存するには、引換券モニターDBで作成された
smaregi_contract_idを使用する必要があります。
したがって、contract_idsmaregi_contract_idは異なるという点に注意してください。
また、smaregi_store_idstore_idも異なるため注意してください。

店舗情報を更新するときはいつでも、次のようにしてください。

Store::updateOrCreate(
    [
        'id' => $store->id,
    ],
    [
        'smaregi_store_name' => $data->smaregi_store_name
    ]
);

次のようにしないよう注意してください。
こうすると違う契約IDの情報で更新してしまいます。

Store::updateOrCreate(
    [
        'smaregi_store_id' => $store->smaregi_store_id], 
    [
        'smaregi_store_name' => $data->smaregi_store_name
    ]
);

APIエンドポイントについての詳細はこちらでも確認できます。

エンドポイントURLは、アプリの開発環境によって異なります。
この記事では、Sandbox環境について例を記載しています。

Sandbox環境 本番環境
アクセストークンAPI https://id.smaregi.dev https://id.smaregi.jp
プラットフォームAPI https://api.smaregi.dev https://api.smaregi.jp

#ソースコード

今回の記事に関するソースコードはGithubに載せております。
よかったらご利用ください!


第3回目は認証機能の実装についてお送りしました。
次回はアプリの実装についてお届けします!

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?