0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OAuthとは!?サービス連携の4ステップをLaravelコードで見る

Last updated at Posted at 2025-11-25

OAuthとは!?

例えば以下のようにサービスを利用されているとします

  • ユーザー: 普段サービスを使っている人
  • サービスA: 連携元のサービス(例: SNS、カレンダーアプリなど)
  • サービスB: サービスAのデータを「連携」したい別サービス(例: 分析ツール、ダッシュボードアプリなど)

ゴールはシンプルで、

サービスBがユーザーの代わりにサービスAのAPIを呼べるようにしたい
でも、ユーザーのパスワードはサービスBに教えたくない

という状況を、安全に実現するのが OAuth 2.0 です。


流れ(Authorization Code フローのイメージ)

一般的によく使われるのが「Authorization Code フロー」と呼ばれるパターンです。
ざっくりした流れは次のとおりです。

ステップ ユーザー サービスB サービスA
「サービスAと連携」ボタンを押す ← リクエスト受信
サービスAのログイン画面へリダイレクト ← リダイレクト受信
サービスAの画面でID/パスワード入力 ← ログイン情報受信
「このユーザーはOK」と判断
→ 認可コード/トークン発行
← 認可コード/トークン受信 ← 認可コード/トークン受信
「トークン」だけ保管
(パスワードは知らない)
トークンを使ってAPI呼び出し ← APIリクエスト受信
  • ユーザーの パスワードはサービスAのログイン画面にだけ入力される
  • サービスBはパスワードを一切見ないし保存もしない
  • 代わりに、「どの操作をどこまでしていいか」が書かれたアクセストークンだけを受け取る

このアクセストークンが、いわゆる「鍵」だと考えると分かりやすいです。

  • 本物のパスワード = 家の「マスターキー」
  • アクセストークン = 「この部屋だけ入ってOK」と書かれたカードキー

サービスBはマスターキー(パスワード)は持たず、カードキー(トークン)だけを使って、
許可された範囲のAPIだけを呼び出します。


なにが嬉しいのか

この「パスワードを教えずに、鍵だけ渡す」形にすることで、次のようなメリットがあります。

  • パスワードを預かるサービスが減る
    → どこか1つが情報漏えいしても、他サービスへの被害を抑えやすい

  • 権限を細かく制御できる
    → 「読み取り専用だけ」「このAPIだけ」など、トークンにスコープを付けて制限できる

  • 連携の解除がしやすい
    → パスワードを変えなくても、トークンだけ無効化すれば特定のサービスBとの連携だけを切れる

まとめると、OAuth 2.0 は、

「パスワード」ではなく「アクセス権の書かれた鍵(トークン)」だけを連携先サービスに渡す仕組み

だとイメージしておくと理解しやすいです。


サービスB(連携する側)のバックエンド処理イメージ(Laravel/PHP)

ざっくりした流れは次の4ステップです。

  1. ユーザーがサービスBの画面で「サービスAと連携」ボタンを押す
  2. サービスBが認可画面のURLにリダイレクトし、コールバックで 認可コードcode)を受け取る
  3. その認可コードを使って アクセストークン を取得する
  4. 取得したアクセストークンを付けて、サービスAの API をコール する
<?php

namespace App\Services;

use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Exception\GuzzleException;

class ServiceAOAuthClient
{
    private const MAX_RETRY          = 3;
    private const RETRY_INTERVAL_SEC = 2;

    private HttpClient $http;

    // ① 認可コードをもらうためのURLを組み立てる(ここにリダイレクトする)
    public function buildAuthorizeUrl(): string
    {
        $query = http_build_query([
            'response_type' => 'code',
            'client_id'     => config('services.service_a.client_id'),
            'redirect_uri'  => route('oauth.callback'),
            'scope'         => 'profile read',
        ]);

        return config('services.service_a.authorize_url') . '?' . $query;
    }

    // ② まとめて実行(認可コードからアクセストークン取得 → API呼び出し)
    public function send(string $authorizationCode): array
    {
        try {
            for ($attempt = 1; $attempt <= self::MAX_RETRY; $attempt++) {
                logger()->info('ServiceA連携開始', ['attempt' => $attempt]);

                // 1. アクセストークン取得
                $tokenResult = $this->getAccessToken($authorizationCode);
                if ($tokenResult['result'] === false) {
                    if ($attempt < self::MAX_RETRY) {
                        sleep(self::RETRY_INTERVAL_SEC);
                        continue;
                    }
                    throw new \RuntimeException('アクセストークン取得に失敗しました');
                }

                // 2. API呼び出し
                $apiResult = $this->callApi($tokenResult['access_token']);
                if ($apiResult['result'] === false) {
                    if ($attempt < self::MAX_RETRY) {
                        sleep(self::RETRY_INTERVAL_SEC);
                        continue;
                    }
                    throw new \RuntimeException('API呼び出しに失敗しました');
                }

                // 成功したら結果を返して終了
                return ['success' => true, 'result_data' => $apiResult['response_data'] ?? null];
            }

            return ['success' => false, 'result_data' => null];

        } catch (\Exception $e) {
            logger()->error('ServiceA連携エラー', ['message' => $e->getMessage()]);
            return ['success' => false, 'result_data' => $e->getMessage()];
        }
    }

    // 認可コードからアクセストークンを取得するイメージ
    private function getAccessToken(string $authorizationCode): array
    {
        try {
            $response = $this->http->post(config('services.service_a.token_url'), [
                'form_params' => [
                    'grant_type'    => 'authorization_code',
                    'code'          => $authorizationCode,
                    'redirect_uri'  => route('oauth.callback'),
                    'client_id'     => config('services.service_a.client_id'),
                    'client_secret' => config('services.service_a.client_secret'),
                ],
            ]);

            $data        = json_decode((string) $response->getBody(), true);
            $accessToken = $data['access_token'] ?? null;

            return ['result' => true, 'access_token' => $accessToken];

        } catch (GuzzleException $e) {
            logger()->warning('アクセストークン取得失敗', ['message' => $e->getMessage()]);
            return ['result' => false, 'access_token' => null];
        }
    }

    // アクセストークンを付けてサービスAのAPIを叩くイメージ
    private function callApi(string $accessToken): array
    {
        try {
            $response = $this->http->get('/v1/profile', [
                'headers' => [
                    'Authorization' => 'Bearer ' . $accessToken,
                    'Accept'        => 'application/json',
                ],
            ]);

            $data = json_decode((string) $response->getBody(), true);

            return ['result' => true, 'response_data' => $data];

        } catch (GuzzleException $e) {
            logger()->warning('API呼び出し失敗', ['message' => $e->getMessage()]);
            return ['result' => false, 'response_data' => null];
        }
    }
}

OAuth 1.0 と OAuth 2.0 のざっくり比較表

最後に、よく聞かれる「1.0 と 2.0 の違い」をざっくり表にしておきます。

観点 OAuth 1.0 OAuth 2.0
リクエスト保護 リクエストごとに秘密の鍵つきハッシュを付ける HTTPS 前提+Bearerトークン(Authorization: Bearer ...
実装の難易度 署名生成が複雑で実装が重い トークンをヘッダに乗せるだけでシンプル
フローの種類 固定的(リクエストトークン → アクセストークン) Authorization Code / Client Credentials / Password など複数
主な利用シーン あまり使われていない Google / GitHub / Salesforce など、現在主流のAPI連携ほぼすべて
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?