さて、スマレジのデータを利用したアプリの開発について、
第3回目はスマレジ引換券モニターアプリの認証機能の実装についてお届けしたいと思います!
アプリ開発の中でも特につまづきやすいところかと思います!
注意点などもまとめておりますでぜひ参考にしてみてください!
※過去記事はこちら
第1回【スマレジアプリを作ってみた#1】アプリの登録とPostman設定
第2回【スマレジアプリを作ってみた#1】引換券モニターアプリの仕様と構成
データベース構造
まず最初に、認証機能の実装に必要なデータベース構造について見ていきましょう。
ER図のように、ユーザー情報とログイン情報を保存するためのauth_users
テーブルと 、
スマレジ契約情報を保存するためのsmaregi_contracts
テーブルがあります。
smaregi_contracts
とauth_users
は1対多のリレーションシップとなります。
認証フロー
スマレジAPIを介してアプリがユーザーのデータにアクセスするには、
アプリアクセストークンを取得する必要があります。
この記事全体を通して、アクセストークンを取得する方法と、
アクセストークンを取得するために必要な手順等について説明します。
まずは次の図で、このアプリに関する認証フローの概要を把握しましょう。
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にリダイレクトした後、ユーザーはスマレジにログインします。
ログイン後、次の認証ページが表示されます。
パラメーター | 値の説明 |
---|---|
response_type | code |
client_id | アプリのクライアントID |
scope | ユーザーに認可を要求するスコープ。複数の場合は半角スペースで結合して指定。 |
スコープ定義
スコープは、アプリによるユーザーデータへのアクセスを制限するために使用されます。
このアプリでは、OpenIDConnectで指定された
openid
email
offline_access
のスコープを使用して、APIプラットフォームでアクセスを取得します。
スコープ | 説明 |
---|---|
openid | ログインユーザーの識別子の取得、UserInfoエンドポイントへのアクセス。 |
ログインユーザーのアカウントのメールアドレスを参照。openid の指定が必須。 |
|
offline_access | リフレッシュトークンの取得。 |
スマレジはクレデンシャルを確認します。
ユーザーが有効な場合、authorization_code
は
スマレジアプリで定義されたリダイレクトURIを介してcode
としてアプリに送信されます。
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_type
がauthorization_code
になっているかを確認してください。
※client_id
とclient_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_type
がclient_credentials
になっているかを確認してください。
※client_id
とclient_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_id
とsmaregi_contract_id
は異なるという点に注意してください。
また、smaregi_store_id
とstore_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回目は認証機能の実装についてお送りしました。
次回はアプリの実装についてお届けします!