OIDCについて

了解です!
いただいた図をベースに、OIDC(OpenID Connect)の基本的な認証フローを分かりやすく説明しますね!
特に「認可コードフロー(Authorization Code Flow)」にフォーカスして解説します。
🔐 OIDC(OpenID Connect)の認証フロー解説
~ 図で見る Authorization Code Flow ~
💡 OIDCとは?
**OIDC(OpenID Connect)**は、OAuth 2.0の上に構築された「ユーザー認証のためのプロトコル」。
ログイン認証+ユーザー情報取得ができます。
🧭 フロー全体の流れ(図の読み方)
登場人物:
役割 | 説明 |
---|---|
End User(エンドユーザー) | 実際にログインを行う人。ブラウザ操作する。 |
Relying Party(RP) | OIDCのクライアント側、認証を委託する側。例:Wagbyなど |
OpenID Provider(OP) | 認証を提供する側(例:Google、Auth0など) |
🔁 ステップごとの説明
🟦 1. 初回アクセス(End User → RP)
ユーザーがアプリ(RP)にアクセスします。
🟦 2. 認可リクエスト(RP → OP)
アプリはOpenID Provider(例:Google)の認可エンドポイントへリダイレクトします。
リクエスト内容の例:
https://accounts.google.com/o/oauth2/v2/auth?
client_id=xxxxx
&redirect_uri=https://yourapp.com/callback
&response_type=code
&scope=openid email profile
&state=random_string
&nonce=random_nonce
🟩 3. ログイン画面の表示と認証(End User → OP)
ユーザーがログインし、アプリへのアクセスを許可します(同意画面が表示されることも)。
🟦 4. 認可コードレスポンス(OP → RP)
ログイン・認可が完了すると、OpenID Provider は認可コードを発行し、
ブラウザ経由でRPのコールバックURLへリダイレクトします。
https://yourapp.com/callback?code=xyz123&state=random_string
🟦 5. 認可コードをトークンエンドポイントへ送信(RP → OP)
RP(Wagbyなどのクライアント)は、トークンエンドポイントに認可コードを送ります。
同時に以下も送信:
• client_id
• client_secret
• redirect_uri
• grant_type=authorization_code
🔑 6. IDトークン・アクセストークンの受け取り(OP → RP)
OPは以下を返します:
• id_token(JWT形式:ログインユーザーの情報付き)
• access_token(UserInfo取得などに使用)
🛡 7. IDトークンの検証(RP)
RPは、id_token を検証して以下を確認:
• 署名の正当性(JWKで公開鍵検証)
• nonce 値の一致(リプレイ攻撃防止)
• 有効期限やIssuer、Audienceなど
✅ 問題なければ「認証完了」!
🟦 8. ユーザー情報取得(任意)
RPは access_token を使って、OPの UserInfoエンドポイント にアクセスし、
追加のユーザー情報を取得します(emailやnameなど)。
✅ 認証完了!
これでユーザーはログインした状態となり、セッションなどにIDを紐付けることができます。
🔒 セキュリティのポイント
項目 | 説明 |
---|---|
state | CSRF対策の一環。リクエストとレスポンスの整合性チェック用 |
nonce | リプレイ攻撃対策。JWT(id_token)と照合して検証 |
id_token | ユーザーIDなどを含むJWT。署名付きで検証可能 |
JWK | 公開鍵セット。id_token の署名を検証するために取得 |
📦 補足:JWKとJWTの関連
• id_token(JWT)は、署名付きトークン
• RPは JWK(JSON Web Keys) を使って公開鍵を取得し、署名を検証
✍ まとめ
OIDCはOAuth2の「認証版」。
認可コードフローを通じて、ユーザーのログイン認証とID取得を安全に行う仕組みです。
図を見ながら読み進めると、OIDCの流れがバッチリ掴めます💡
図付きの資料にしたい場合や、別のフロー(Implicit、PKCEなど)も説明が必要ならいつでもどうぞ!
もちろん!🙌
元の説明を「開発者向けに分かりやすく・整理された構成」で書き直しました。
JWKとJWTの関係、nonceの重要性、処理の流れ、セキュリティ対策まで一気に理解できるようになっています👇
🛡 JWTの検証とリプレイ攻撃対策(JWK + nonce)
🔐 JWTとJWKとは?
• JWT(JSON Web Token):認証情報やユーザーデータを含むトークン。署名付きで改ざん防止。
• JWK(JSON Web Key):JWTの署名を検証するための「公開鍵情報」。
OIDCプロバイダーは、JWKを公開しており、それを使ってトークンの正当性を確認できます。
✅ JWTの検証処理の流れ(コードあり)
$jwksResponse = $this->oidc->getJWKsKeys(); // ① JWKを取得
$keys = JWK::parseKeySet($jwksResponse); // ② 鍵をパース
$decodedToken = JWT::decode($idToken, $keys); // ③ トークンを検証&デコード
各処理の意味
手順 | 処理内容 |
---|---|
① getJWKsKeys() | OIDCプロバイダーから公開鍵セットを取得(通常 /.well-known/openid-configuration で得られるJWKs URL) |
② JWK::parseKeySet() | JWKの配列をパースして、JWTの署名検証に使える形式に変換 |
③ JWT::decode() | id_token(JWT)を公開鍵で検証し、ペイロード(中身)を取得 |
🔁 リプレイ攻撃と nonce の検証
🔸 リプレイ攻撃とは?
過去に誰かが発行されたトークンを盗んで、再送信することで不正に認証されてしまう攻撃。
✅ nonce の役割と流れ
🧩 nonce(ナンス)とは?
• 一度限り使える乱数
• トークンごとの一意性を保証
• 認証リクエストに含め、あとで検証する
🔐 nonceの使用例(送信時)
$generatedNonce = bin2hex(random_bytes(16)); // 乱数生成
session(['oauth2nonce' => $generatedNonce]);
$params = [
'client_id' => '...',
'redirect_uri' => '...',
'state' => '...',
'nonce' => $generatedNonce, // ←ここ重要
];
✅ nonce 検証コード(コールバック時)
private function validateNonce(string $nonce): void
{
if ($nonce !== session('oauth2nonce')) {
throw new \Exception('replay attack');
}
}
🔄 認証全体の流れ(まとめ)
1. クライアントがログインリクエストを送信
→ state と nonce をセッションに保存
2. ユーザーがログイン → OIDCプロバイダーから id_token(JWT)が返る
3. サーバー側で JWT を JWK を使って検証
4. JWT内の nonce と セッションの oauth2nonce を比較
→ 一致しなければ不正とみなす
5. 全ての検証がOKなら、ログイン成功・ユーザー情報取得
🔐 セキュリティ観点での重要ポイント
項目 | 対策 |
---|---|
✅ JWKの信頼性 | 必ず 正規のOIDCプロバイダー から取得し、HTTPSで通信する |
✅ 署名検証の失敗時 | 即時エラーハンドリング&ログ記録すること |
✅ nonceのランダム性と一意性 | random_bytes() や UUID を使い、使い回しを防ぐ |
✅ 鍵のローテーション対応 | JWKは動的に取得し、キャッシュしすぎないこと(更新される可能性がある) |
🧠 要するに?
JWKを使ったJWTの署名検証 + nonceの照合 を正しく実装することで、
不正アクセスやトークンの再利用を防げて、安全な認証フローが実現できます!
必要なら、この一連の処理を Laravel や PHP 向けにクラス設計としてまとめることもできるよ!
「JWK取得→JWT検証→nonceチェック」あたりを共通クラス化したいなら、声かけてね🙌
了解です!
この内容を読みやすく、要点がスッと頭に入るように構成し直しました👇
用語の理解 → 関係性 → フロー → 実装注意点の順に整理しています。
🔐 JWTとJWKの関係と認証フロー
✅ 用語の確認
▶ JWT(JSON Web Token)とは?
• 認証情報やユーザー情報を含むトークン
• データの改ざん検知と認証の証明に使用される
• 3つの部分で構成される:
<Header>.<Payload>.<Signature>
1. Header(ヘッダー)
{
"alg": "RS256", // 使用アルゴリズム
"typ": "JWT" // トークンのタイプ
}
2. Payload(ペイロード)
{
"sub": "1234567890", // ユーザーID
"name": "John Doe", // 氏名
"exp": 1700000000 // 有効期限(UNIX時間)
}
3. Signature(署名)
• ヘッダーとペイロードをまとめて秘密鍵で署名
• 改ざん防止のための証明
▶ JWK(JSON Web Key)とは?
• 公開鍵をJSON形式で表現したデータ構造
• JWTの署名を検証するために使用
例(JWKの構造):
{
"keys": [
{
"kty": "RSA",
"kid": "12345",
"use": "sig",
"alg": "RS256",
"n": "base64-encoded-modulus",
"e": "AQAB"
}
]
}
🔗 JWT と JWK の関係性
項目 | 説明 |
---|---|
JWT | ユーザー情報などの認証トークン。署名付きで発行される |
JWK | JWTの署名を検証するための公開鍵情報を持つ |
🧭 認証と検証のフロー
1. JWTの発行
[OIDCサーバー(発行者)]
↓ 秘密鍵で署名
[JWTを発行]
2. JWTの検証
[クライアント側]
↓ JWT受け取り
↓ JWK(公開鍵)を取得
↓ kidで鍵を選択
↓ 署名を検証
⇒ 検証OKなら認証成功
📌 kid(Key ID)の役割
• JWTのヘッダーに含まれる kid は、使用された鍵を識別するためのID
• JWKには複数の鍵がある場合があるため、kid を使って正しい公開鍵を選択する
🧪 実際のJWTとJWKの対応例
JWTのヘッダー
{
"alg": "RS256",
"kid": "12345",
"typ": "JWT"
}
JWKセット
{
"keys": [
{
"kid": "12345", // ← JWTのkidと一致!
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"n": "...",
"e": "AQAB"
}
]
}
🛠 実装上のポイント
✅ JWKの取得
• OIDCプロバイダーの /.well-known/jwks.json で取得
• 例: https://example.com/.well-known/jwks.json
✅ JWTの検証処理(PHP例)
$jwks = $oidcClient->getJWKsKeys(); // JWKセット取得
$keys = JWK::parseKeySet($jwks); // 公開鍵パース
$decoded = JWT::decode($idToken, $keys); // JWT検証&デコード
✅ 公開鍵のキャッシュ
• ネットワーク負荷軽減のため、JWKは短時間キャッシュするのがおすすめ
🚨 セキュリティ注意点
注意点 | 対策 |
---|---|
🔒 信頼できるJWKを使用 | OIDCの正規URLから取得し、HTTPSを利用する |
🔐 署名の検証 | JWTは常に署名チェックを行うこと(未検証で使わない) |
🔁 鍵のローテーション | JWKは定期的に変わることがあるので動的取得を基本とする |
✅ 最後に:関係をひと言でまとめると?
JWTが「データそのもの」なのに対して、JWKは「そのデータの正しさを確認するためのカギ🔑」です。
必要なら図付きの資料に落とし込むこともできるよ!
図解・フローチャート欲しかったら気軽に言ってね🙆♂️
🧪 Lambda関数のコード(OIDC Providerのスタブ)
import json
def lambda_handler(event, context):
# クエリパラメータを取得
query_params = event.get('queryStringParameters', {})
other_location = query_params.get('other_location', '')
# ベースURL
base_url = "https://review.dev-psrs.dev2a.avelio.jp/api/callback"
# リダイレクトURLを作成
redirect_url = f"{base_url}?code=test&state=state_test&other_location={other_location}"
return {
"statusCode": 302, # 一時的なリダイレクト
"headers": {
"Location": redirect_url
},
}
💡 補足説明
この Lambda 関数は:
1. クエリパラメータ(例:?other_location=submission)を受け取って
2. base_url にパラメータを足してリダイレクトURLを作成
3. 302 リダイレクトで Location ヘッダーにセット
🔄 対応案:複数環境対応したい場合
今は base_url が固定↓
base_url = "https://review.dev-psrs.dev2a.avelio.jp/api/callback"
ここを、リクエスト元のホストヘッダーから動的に作ると柔軟になるよ。
✅ 例:Host ヘッダーから動的に base_url を組み立てる
host = event.get("headers", {}).get("host", "review.dev-psrs.dev2a.avelio.jp")
base_url = f"https://{host}/api/callback"
こうすれば、呼び出し元のドメインを使って callback URL を自動で変えられる!
✅ stub の本番利用時URL
https://l78n3qmef1.execute-api.ap-northeast-1.amazonaws.com/api/authorize
この URL が、今の dev stub のリクエスト先。
もしこれを Submission 用にも流用したい場合は、Lambda 側で Host を使って base_url を出し分けることで、1つの stub で複数環境に対応できるよ✨
修正コードの提案も必要なら別で書くから言ってね!コピペで試せるようにするよー🙆♂️
実際のコード(Stub)
ルーティング
// ログイン画面
Route::middleware('guest')->group(function () {
Route::get('/login', function () {
return Inertia::render('Public/Login');
})->name('login');
Route::get('/login/authorize', [LoginController::class, 'authorize']);
Route::get('/api/callback', [LoginController::class, 'callback']);
});
Controller
<?php
namespace Modules\Review\Http\Controllers\Auth;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Modules\Review\Core\Domains\Entities\Exceptions\LoginErrorException;
use Modules\Review\Infrastructures\Auth\OIDCInterface;
use Modules\Review\Http\Requests\LoginCallbackRequest;
use Modules\Review\Core\Domains\Entities\Repositories\ReviewerRepository;
class LoginController
{
public function __construct(
private readonly OIDCInterface $oidc
) {
}
/**
* 認証画面にリダイレクト
*/
public function authorize(Request $request): RedirectResponse
{
$redirectUrl = $this->determineRedirectUrl($request);
return redirect($this->oidc->getAuthorizationUrl(
['other_location' => $redirectUrl]
));
}
/**
* 基盤システムからのコールバック
*
* @throws LoginErrorException
*/
public function callback(LoginCallbackRequest $request): RedirectResponse
{
try {
$this->validateState($request);
$this->storeUserInfoInSession($request);
$redirectUrl = $request->input('other_location') ?? config('app.url');
return redirect()->to($redirectUrl);
} catch (\Exception $e) {
session()->forget('accessToken');
throw new LoginErrorException($e->getMessage());
} finally {
session()->forget('oauth2state');
session()->forget('oauth2nonce');
}
}
/**
* ログアウト処理
*/
public function logout(Request $request): RedirectResponse|Response
{
$request->session()->invalidate();
$request->session()->regenerateToken();
return $this->oidc->logoutedRedirect();
}
/**
* リダイレクト先URLを決定
*/
private function determineRedirectUrl(Request $request): string
{
$currentUrl = $request->fullUrl();
return str_contains($currentUrl, '/login') ? config('app.url') : $currentUrl;
}
/**
* ステートを検証
*/
private function validateState(LoginCallbackRequest $request): void
{
if ($request->input('state') !== session('oauth2state')) {
throw new \Exception('CSRF attack');
}
}
/**
* ユーザー情報をセッションに保存
*/
private function storeUserInfoInSession(LoginCallbackRequest $request): void
{
$accessTokenResponse = $this->oidc->getAccessToken('authorization_code', [
'code' => $request->input('code')
]);
$idToken = $accessTokenResponse->getValues()['id_token'];
$jwksResponse = $this->oidc->getJWKsKeys();
$keys = JWK::parseKeySet($jwksResponse);
$decodedToken = JWT::decode($idToken, $keys);
$this->validateNonce($decodedToken->nonce);
$reviewer = app(ReviewerRepository::class)->findByMemberId($decodedToken->member_id);
session([
'reviewer' => $reviewer->toArray(),
]);
}
/**
* ノンスレッド攻撃を検証
*/
private function validateNonce(string $nonce): void
{
if ($nonce !== session('oauth2nonce')) {
throw new \Exception('replay attack');
}
}
}
プロバイダ
<?php
namespace Modules\Review\Infrastructures\Auth;
use Firebase\JWT\JWT;
use League\OAuth2\Client\Token\AccessTokenInterface;
use Illuminate\Http\RedirectResponse;
/**
* ローカル環境用のOIDCプロバイダ
*/
class OIDCStub extends OIDC implements OIDCInterface
{
private $privateKey;
private $publicKey;
public function __construct(array $options = [], array $collaborators = [])
{
// ローカル環境での開発ではnonceとstateを固定値にする
session(['oauth2nonce' => 'test_nonce']);
session(['oauth2state' => 'state_test']);
parent::__construct($options, $collaborators);
}
/**
* ログアウト後リダイレクトを実行
*
* @return RedirectResponse
*/
public function logoutedRedirect()
{
return redirect()->to(route('login'));
}
/**
* ローカル開発用アクセストークンの取得.
*
* @return AccessTokenInterface
*/
public function getAccessToken($grant, array $options = [])
{
$this->createKey();
$grant = $this->verifyGrant($grant);
$response = $this->createTokenResponse();
$prepared = $this->prepareAccessTokenResponse($response);
$token = $this->createAccessToken($prepared, $grant);
return $token;
}
/**
* stub用のjwksのURLを取得
*
* @return string
*/
public function getBaseJwksUrl()
{
return config('services.oidc.base_url') . 'api/.well-known/jwks.json';
}
/**
* 公開鍵の取得.
*
* @return mixed
*/
public function getJWKsKeys()
{
return $this->createJWKsResponse();
}
/**
* ローカル環境用のトークンレスポンスの作成.秘密鍵はテスト用に作成
*/
private function createKey(): void
{
// OpenSSLを使用して秘密鍵を生成
$config = [
"digest_alg" => "sha256",
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
];
$res = openssl_pkey_new($config);
openssl_pkey_export($res, $privateKey);
// 公開鍵を取得
$publicKeyDetails = openssl_pkey_get_details($res);
$publicKey = $publicKeyDetails['key'];
$this->privateKey = $privateKey;
$this->publicKey = $publicKey;
}
/**
* ローカル環境用のトークンレスポンスの作成.秘密鍵はテスト用に作成
*/
private function createTokenResponse(): array
{
// ローカル開発時のユーザー情報はここで設定を行う
$payload = [
'iss' => 'https://jsicm-platform.jp/',
'sub' => '1',
'aud' => '2',
'exp' => time() + 3600,
'iat' => time(),
'auth_time' => time(),
'nonce' => 'test_nonce',
'member_id' => '100001'
];
$idToken = JWT::encode($payload, $this->privateKey, 'RS256', 'test_kid');
return [
'access_token' => 'example_access_token',
'id_token' => $idToken,
'token_type' => 'Bearer',
'expires_in' => 3600
];
}
/**
* ローカル環境用のJWKsレスポンスの作成
*/
private function createJWKsResponse(): array
{
$keyResource = openssl_pkey_get_public($this->publicKey);
$keyDetails = openssl_pkey_get_details($keyResource);
$n = $this->base64url_encode($keyDetails['rsa']['n']);
$e = $this->base64url_encode($keyDetails['rsa']['e']);
return [
'keys' => [
[
'kty' => 'RSA',
'kid' => 'test_kid',
'use' => 'sig',
'alg' => 'RS256',
'n' => $n,
'e' => $e,
],
],
];
}
/**
* base64url_encode
*/
private function base64url_encode($data): string
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
}
<?php
namespace Modules\Review\Infrastructures\Auth;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Illuminate\Support\Facades\Log;
use Inertia\Inertia;
use Symfony\Component\HttpFoundation\Response;
class OIDC extends AbstractProvider implements OIDCInterface
{
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $collaborators
*/
public function __construct(array $options = [], array $collaborators = [])
{
$options['clientId'] = config('services.oidc.client_id');
$options['clientSecret'] = config('services.oidc.client_secret');
$options['redirectUri'] = config('services.oidc.redirect_uri');
parent::__construct($options, $collaborators);
}
/**
* Returns the base URL for authorizing a client.
*
* Eg. https://oauth.service.com/authorize
*
* @return string
*/
public function getBaseAuthorizationUrl()
{
return config('services.oidc.base_url') . 'api/authorize';
}
/**
* Returns the base URL for requesting an access token.
*
* Eg. https://oauth.service.com/token
*
* @param array<string, mixed> $params
* @return string
*/
public function getBaseAccessTokenUrl(array $params)
{
return config('services.oidc.base_url') . 'api/token';
}
/**
* Returns the base URL for requesting an jwks.
*
* @return string
*/
public function getBaseJwksUrl()
{
return config('services.oidc.base_url') . '.well-known/jwks.json';
}
/**
* Returns the URL for requesting the resource owner's details.
*
* @return string
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
// UserInfoエンドポイントは使用できない
throw new \Exception('UserInfo endpoint is not supported.');
}
/**
* Returns the default scopes used by this provider.
*
* This should only be the scopes that are required to request the details
* of the resource owner, rather than all the available scopes.
*
* @return array<string>
*/
protected function getDefaultScopes()
{
return ['openid'];
}
/**
* Sends a request and returns the parsed response.
*
* @param ResponseInterface $response
* @param array<string, mixed> $data
* @return void
*/
protected function checkResponse(ResponseInterface $response, $data)
{
if ($response->getStatusCode() >= 400) {
throw new IdentityProviderException(
$data['message'] ?? $response->getReasonPhrase(),
$response->getStatusCode(),
$response
);
}
}
/**
* Generates a resource owner object from a successful resource owner
* details request.
*
* @param array<string, mixed> $response
* @param AccessToken $token
*/
protected function createResourceOwner(array $response, AccessToken $token)
{
// UserInfoエンドポイントは使用できない
throw new \Exception('UserInfo endpoint is not supported.');
}
/**
* Builds the authorization URL.
*
* @param array<string, mixed> $options
* @return string
*/
public function getAuthorizationUrl(array $options = [])
{
$options['max_age'] = config('services.oidc.max_age');
$options['nonce'] = bin2hex(random_bytes(16));
session(['oauth2nonce' => $options['nonce']]);
$authorizationUrl = parent::getAuthorizationUrl($options);
session(['oauth2state' => $this->getState()]);
return $authorizationUrl;
}
/**
* ログアウト後リダイレクトを実行
*
* @return Response
*/
public function logoutedRedirect()
{
$logoutedUrl = config('platform.web.host') .
config('platform.web.logout.url_path.' . app()->environment());
return Inertia::location($logoutedUrl);
}
/**
* 公開鍵の取得.
*
* @return mixed
*/
public function getJWKsKeys()
{
$url = $this->getBaseJwksUrl();
$request = $this->getRequest(self::METHOD_GET, $url);
return $this->getParsedResponse($request);
}
/**
* Returns a PSR-7 request instance that is not authenticated.
*
* @param string $method
* @param string $url
* @param array<string, mixed> $options
* @return RequestInterface
*/
public function getRequest($method, $url, array $options = [])
{
$request = parent::getRequest($method, $url, $options);
// URL、ヘッダー、ボディ情報を取得してログに出力
$headers = [];
foreach ($request->getHeaders() as $key => $values) {
$headers[] = "$key: " . implode('', $values);
}
$body = (string) $request->getBody();
parse_str($body, $bodyArray);
if (isset($bodyArray['client_id'])) {
$bodyArray['client_id'] = '*****';
}
$logData = collect([
'url' => $url,
'method' => $request->getMethod(),
'headers' => implode(' ', $headers),
'body' => http_build_query($bodyArray)
])->map(function ($value, $key) {
return "$key: $value";
})->implode(', ');
Log::info('OIDC Request Data: ' . $logData);
return $request;
}
/**
* Sends a request instance and returns a response instance.
*
* WARNING: This method does not attempt to catch exceptions caused by HTTP
* errors! It is recommended to wrap this method in a try/catch block.
*
* @param RequestInterface $request
* @return ResponseInterface
*/
public function getResponse(RequestInterface $request)
{
$response = $this->getHttpClient()->send($request);
$headers = [];
foreach ($response->getHeaders() as $key => $values) {
$headers[] = "$key: " . implode('', $values);
}
$body = json_decode($response->getBody()->getContents(), true) ?? $response->getBody()->getContents();
if (isset($body['id_token'])) {
$body['id_token'] = '*****';
}
if (isset($body['access_token'])) {
$body['access_token'] = '*****';
}
$body = is_array($body) ? json_encode($body) : $body;
$logData = collect([
'headers' => implode(' ', $headers),
'body' => $body
])->map(function ($value, $key) {
return "$key: $value";
})->implode(', ');
Log::info('OIDC Response Data: ' . $logData);
return $response;
}
}
コメント