Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Laravelで学ぶOAuthとOpenID Connectの基礎と実装

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Laravelで学ぶOAuthとOpenID Connectの基礎と実装

PHPerKaigi 2026
OAuthやOpenID Connectは、現代のWebアプリケーションで重要な役割を果たしていますが、これらの概念はしばしば誤解されがちです。
本セッションでは、これらの技術の基礎をしっかりと理解し、Laravelでの実装を通じて実践的な知識を得ることを目指します。
・OAuthとは
・OpenID Connectとは
・Laravelでの実装例

Avatar for Koji Yoshida

Koji Yoshida

March 21, 2026
Tweet

More Decks by Koji Yoshida

Other Decks in Technology

Transcript

  1.   2 X: @theyoshida3 • フリーでは珍しい PHPer • 担当するプロダクトは PHP

    と Go • 最近よく触るのは Terraform • 好きなお酒は メガジョッキハイボール • 愛⽤しているプロテインは X-PLOSION the よしだ Koji Yoshida フリー株式会社
  2.   3 • OAuth と OpenID Connect の違いを理解する • Laravel

    での実装イメージを掴む 本⽇のゴール
  3. 7 認証(Authentication) 本⼈確認 • ID/PW でのログイン • ⽣体認証 • SMS

    認証 認可(Authorization) 権限の移譲 • Googleドライブで「閲覧のみ」の権限を付与 • サードパーティアプリで Googleフォトの写真を読み込む • フリーナンス by freee で freee請求書の請求書を読み込む 認証と認可の違い
  4. 9 スコープ スコープ = 権限の範囲 スコープ名と紐づく権限はサービスごとに異なる Githubの場合 ⽤語の解説 スコープ 権限の内容

    repo 全権限 公開・非公開リポジトリの 読み書き、管理すべて public_repo 公開リポジトリのみの書き込みアクセス repo:status コミットステータス(CIの結果など)の読み書き repo_deployment デプロイステータスの読み書き
  5. 10 登場⼈物 • リソースオーナー ◦ ユーザー(データ所有者) • クライアント ◦ 許可を得てリソースにアクセスするアプリ

    • 認可サーバー ◦ ユーザーを認証し、認可コードやトークンを発⾏するサーバー • リソースサーバー ◦ アクセストークンを検証し、実際のデータ(プロフィール、写真など)を提 供するサーバー ⽤語の解説
  6. 14 Google の認可サーバーへアクセスする場合 認可サーバーへアクセス GET /o/oauth2/v2/auth ?response_type=code &client_id={client_id} &scope={scope} &redirect_uri={redirect_uri}

    &state={state} HTTP/1.1 Host: accounts.google.com • response_type 認可コードグラントは「code」固定 • client_id 事前に認可サーバーに登録されるクライアント ID • scope アクセスを許可してほしい情報の範囲 • redirect_uri 事前に認可サーバーに登録されるリダイレクト URI • state クライアントで⽣成するランダムな⽂字列 認可レスポンス時に値チェック
  7. 16 Google の認可サーバーレスポンスの場合 認可レスポンス HTTP/1.1 302 Found Location: {redirect_uri} ?code={authorization_code}

    &state={state} • redirect_uri リダイレクト先 • authorization_code アクセストークンと交換するためのコード 有効期限は短く、⼀度使うと無効になる • state 認可サーバーへアクセス時に⽣成したもの 値チェック(CSRF 対策) この後のリダイレクト先でアクセストークン取得を実施
  8. 18 Google のトークンリクエストの場合 トークンリクエスト POST /token ?grant_type=authorization_code &code={authorization_code} &client_id={client_id} &client_secret={client_secret}

    &redirect_uri={redirect_uri} HTTP/1.1 Host: oauth2.googleapis.com Content-Type: application/x-www-form-urlencoded • grant_type 「authorization_code」固定 • code 認可レスポンスで取得した authorization_code • client_id 事前に認可サーバーに登録されるクライアント ID • client_secret 事前に認可サーバーから発⾏されるシークレット • redirect_uri 事前に認可サーバーに登録されるリダイレクト URI
  9. 19 • Authorization ヘッダーでもOK ◦ Authorization: Basic {Base64(client_id:client_secret)} ◦ client_id,

    client_secret はボディに含めない • public clientの場合はAuthorizationやclient_secretは含めないでもOK トークンリクエスト補⾜
  10. 20 Google のトークンレスポンスの場合 トークンレスポンス HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8

    { "access_token": "{access_token}", "expires_in": 3920, "refresh_token": "{refresh_token}", "scope": "https://www.googleapis.com/auth/drive.metadata.readonly", "token_type": "Bearer" }
  11. 21 • access_token API 実⾏時に使⽤するトークン 有効期間は短い • expires_in アクセストークンの有効期限(秒) •

    refresh_token access_token 期限切れの際に再取得するためのトークン 無効になると再認可が必要 • scope アクセスを許可された情報の範囲 OAuth の仕様上、必須ではないが Google の場合は含まれる トークンレスポンス
  12. 23 Google Drive API の場合 リソース要求 GET /drive/v3/files HTTP/1.1 Host:

    www.googleapis.com Authorization: Bearer {access_token} • URL やレスポンスは提供元ごとに独⾃ • Authorization: Bearer {access_token} は共通仕様
  13. 24 現在はドラフト段階 セキュリティ上のベストプラクティスを標準化した内容となっている • 全クライアントでの PKCE 必須化 ◦ これまでは パブリッククライアントのみ推奨

    • 安全性の低いグラントタイプ廃⽌ ◦ インプリシットグラント ◦ リソースオーナーパスワードクレデンシャル • リダイレクト URI の完全⼀致 検証 ◦ これまでは前⽅⼀致が許容されるケースがあった • リフレッシュトークンのセキュリティ要件 ◦ 送信者制限付きリフレッシュトークン発⾏ ◦ リフレッシュトークンのローテーション OAuth 2.1 の変更点
  14. 25 認可コードの横取り(なりすまし)を防ぐための合⾔葉 下記の⼿順でなりすましができてしまう • スマホアプリでは、 my-app://callback?code=xxx (カスタム URL スキー ム)を使う

    • 偽アプリの URL スキームを正規のアプリと同じものとして設定 • 認可コード取得後にブラウザからアプリに戻る際に偽アプリが反応 PKCE とは
  15. 27 • code_verifier ランダムな⽂字列として、クライアントで⽣成 • code_challenge code_verifier を SHA256 でハッシュ化したもの

    ハッシュ化をしないことも可能だが⾮推奨 • code_challenge_method S256 or plain PKCE で登場するパラメーター
  16. 29 Google の認可サーバーへアクセスする場合 認可サーバーへアクセス GET /o/oauth2/v2/auth ?response_type=code &client_id={client_id} &scope={scope} &redirect_uri={redirect_uri}

    &state={state} &code_challenge={code_challenge} &code_challenge_method=S256 HTTP/1.1 Host: accounts.google.com • code_challenge(code_verifier のハッシュ化), code_challenge_method を含める • 認可サーバー側では認可コードと紐づけて保存する
  17. 30 Google のトークンリクエストの場合 トークンリクエスト POST /token ?grant_type=authorization_code &code={authorization_code} &client_id={client_id} &client_secret={client_secret}

    &redirect_uri={redirect_uri} &code_verifier={code_verifier} HTTP/1.1 Host: oauth2.googleapis.com Content-Type: application/x-www-form-urlencoded • code_verifier を含める • 認可サーバーでは code_challenge と⼀致するか検証する • 認可コードの横取り時は検証が失敗する
  18. 32 OAuth をベースに拡張した 認証のための仕組み OIDC とも呼ばれている 特徴 • openid スコープ

    認可リクエストの scope パラメーターに必ず openid を含める • ID トークン ユーザーの⾝元情報が⼊った証明書 • UserInfo エンドポイント 詳細なユーザー情報を取得するための標準化されたエンドポイント OpenID Connect とは
  19. 33 グラントタイプ に相当するものをフローと呼ぶ 今回は「認可コードフロー」を説明 共通する⽤語も多いが、下記は表現が異なる OAuth 2.0 と OIDC の

    ⽤語マッピング OAuth 2.0 OIDC リソースオーナー エンドユーザー クライアント リライイング・パーティ(RP) 認可サーバー OpenID プロバイダー(IdP) リソースサーバー UserInfo エンドポイント
  20. 36 IDプロバイダーへアクセス GET /o/oauth2/v2/auth ?response_type=code &client_id={client_id} &scope=openid profile email &redirect_uri={redirect_uri}

    &state={state} &nonce={nonce} HTTP/1.1 Host: accounts.google.com • scope openid が必須 profile, email, address, phone がある • nonce ID トークンの中に指定した値が記載される ID トークンの検証に利⽤ • リフレッシュトークン取得 Google では独⾃仕様 access_type=offline, prompt=consent
  21. 38 トークンレスポンス HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 { "access_token":

    "{acceess_token}", "expires_in": 3599, "scope": "openid https://www.googleapis.com/auth/userinfo.email ...", "token_type": "Bearer", "id_token": {id_token}, "refresh_token": "{refresh_token}" }
  22. 40 id_token は JWT (JSON Web Token) という形式で、 ドットで区切られた 3

    つのセクションで構成される 1. Header (ヘッダー) アルゴリズム(RS256 など)や、どの鍵で署名したか 2. Payload (ペイロード) 実際のユーザー情報 3. Signature (署名) このトークンが改ざんされていないことの証明 IDトークン (JWT) の構造
  23. 41 IDトークン (ペイロード) の構造 { "iss": "https://accounts.google.com", "aud": {client_id}, "sub":

    "{sub}", "iat": 1353601026, "exp": 1353604926, "nonce": {nonce} } • iss ID プロバイダの URL • aud リライング‧パーティのクライアント ID • sub エンドユーザーの識別⼦ email ではなく sub でユーザーを識別すべし • iat JWT の発⾏時間 • exp ID トークンの有効期限 • nonce 認証リクエスト時に送信した値
  24. 42 • 署名の検証 IdP が公開している公開鍵 (JWK) を取得し、トークンが改ざんされていないか確認 • 発⾏者の確認 iss

    の値が、信頼する IdP の URL と完全に⼀致するか確認 • 対象者の確認 aud の値が、⾃分のアプリの Client ID と⼀致するか確認 • 有効期限の確認 現在時刻が exp(有効期限)を過ぎていないか確認する。 • 整合性の確認 認可リクエスト時に送信した nonce と、 ID トークン内の nonce クレームが完全に⼀致するか確認 • 発⾏時刻の確認 iat が極端に古いものではないか、未来の時刻になっていないかを確認 IDトークンの検証
  25. 45 詳細プロフィール提供 HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 { "sub":

    "{sub}", "name": "Jane Doe", "given_name": "Jane", "family_name": "Doe", "preferred_username": "j.doe", "email": "[email protected]", "email_verified": "true" } 指定した scope に対応したフィールドが返却される 詳細は OpenID Connect Core 1.0 incorporating errata set 2 参照
  26.   47 「この URL を⾒れば、その IdP のすべてのエンドポイント URL が書いてある」という 共通の場所が定義されている

    Google: https://accounts.google.com/.well-known/openid-configuration OpenID Connect Discovery { "issuer": "https://accounts.google.com", "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", "token_endpoint": "https://oauth2.googleapis.com/token", "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo", "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs", "scopes_supported": ["openid", "email", "profile", ...], "response_types_supported": ["code", "token", "id_token", ...] }
  27.   48 OAuth は「認可(権限)」の仕組みであり、「認証(本⼈確認)」を保証しない OIDC ではID トークンの中に、aud(クライアント ID)や nonce を含めることでリスクを防⽌

    OAuth で認証を⾏うリスク • アクセストークンを持っていること = 「本⼈」ではない アクセストークンを使って API でプロフィールが取れた場合でも、 「⽬の前のユーザーがログインした結果」であるという保証がない • すり替え攻撃のリスク アクセストークンを別のアプリで発⾏したものとすり替えられたときに検証できない • プロフィール取得が標準化されていない OAuthには標準化された、OpenID Connect Discovery や Claims が存在しない OAuth でも認証ができるのでは?
  28. 50 • Laravel を使って Google アカウントで OIDC 認証 • ⽣年⽉⽇を追加で取得する

    • User モデルとは別に OAuthProviders モデルを⽤意 Laravel で実装例
  29. 51 Google の認可サーバーを使った認証の事前準備 1. Google Cloud で無料トライアルを申し込む 2. プロジェクトの作成(必要に応じて) 3.

    ブランディングを作成 4. クライアントを作成 5. テストユーザー設定 6. People API 有効化 Google Cloud 事前準備
  30. 53 Google Auth Platform → クライアント callback などの URI を設定

    設定後にクライアントID、シークレットが表⽰ Google Cloud 事前準備: クライアントを作成
  31. 55 OAuth を 簡単に使える公式ライブラリ: https://github.com/laravel/socialite redirect は Google からの redirect(callback)を意味する

    Laravel Socialite の準備 composer require laravel/socialite # config/services.php 'google' => [ 'client_id' => env('GOOGLE_CLIENT_ID'), 'client_secret' => env('GOOGLE_CLIENT_SECRET'), 'redirect' => env('GOOGLE_REDIRECT_URI'), ],
  32. 58 OAuthController (auth/redirect) 認可コード取得 実装 public function redirect() { return

    Socialite::driver('google') ->enablePKCE() ->scopes(['https://www.googleapis.com/auth/user.birthday.read']) ->with([ 'access_type' => 'offline', 'prompt' => 'consent', ]) ->redirect(); }
  33. 59 • PKCE を有効にするため enablePKCE() を指定 • access_type = offline,

    prompt = consent で refresh_token を取得 • scope はデフォルトで openid, profile, email が設定されている • cliend_id や redirect_uri, state 等のパラメーターは Socialite で指定している 認可コード取得 実装
  34. 61 OAuthController (auth/callback) トークン取得と検証 実装 public function callback(Request $request) {

    $googleUser = Socialite::driver('google') ->enablePKCE() ->user(); $googleSub = $googleUser->user['sub'] ?? null; $oauthProvider = OAuthProvider::where('provider', 'google') ->where('provider_user_id', $googleSub) ->first();
  35. 62 トークン取得と検証 実装 if ($oauthProvider) { $user = $oauthProvider->user; }

    else { $user = User::create([ 'email' => $googleUser->getEmail(), 'last_name' => $googleUser->user['family_name'] ?? '', 'first_name' => $googleUser->user['given_name'] ?? '', 'avatar_url' => $googleUser->user['picture'] ?? null, ]); $oauthProvider = OAuthProvider::create([ 'user_id' => $user->id, 'provider' => 'google', 'provider_user_id' => $googleSub, ]); }
  36. 63 • PKCE を有効にするため enablePKCE() を指定 • access_type = offline,

    prompt = consent で JWT に refresh_token が含まれる • ユーザーは email でなく sub で特定 認可コード取得 実装
  37. 64 AbstractProvider トークン取得と検証 実装深堀り public function user() { if ($this->user)

    { return $this->user; } if ($this->hasInvalidState()) { throw new InvalidStateException; } $response = $this->getAccessTokenResponse($this->getCode()); $user = $this->getUserByToken(Arr::get($response, 'access_token')); return $this->userInstance($response, $user); }
  38. 65 トークンリクエストや ID トークンの検証を実施 トークンリクエスト JWT のデコードと署名検証 発⾏者(iss)の検証 オーディエンス(aud)の検証 公開鍵の取得や署名検証を内部で⾏ってくれているのが最⼤の良さ

    nonce 対応は独⾃で⾏う必要があるため注意 (今回は割愛) トークン取得と検証 実装深堀り Socialite::driver('google')->user() $this->getAccessTokenResponse($this->getCode()); # Laravel\Socialite\Two\GoogleProvider $this->getUserByToken(Arr::get($response, 'access_token'));
  39. 67 OAuthController (auth/callback) ユーザー情報取得 実装 $response = Http::withToken($googleUser->token) ->get('https://people.googleapis.com/v1/people/me', [

    'personFields' => 'birthdays', ]); if ($response->successful()) { $data = $response->json(); if (isset($data['birthdays']) && is_array($data['birthdays']) && count($data['birthdays']) > 0) { $birthday = $data['birthdays'][0]['date'] ?? null; if ($birthday && isset($birthday['year'], $birthday['month'], $birthday['day'])) { $dateOfBirth = sprintf('%04d-%02d-%02d', $birthday['year'], $birthday['month'], $birthday['day']); } } }
  40. 72 書籍 • Auth屋 著『雰囲気で OAuth を使っているエンジニアが最新のベストプラク ティス OAuth2.1 を整理して学べる本』

    • Auth屋 著『OAuth、OAuth 認証、OpenID Connect の違いを整理して理解で きる本 [2024 改訂]』 技術解説‧リファレンス • Auth Wiki - https://auth-wiki.logto.io/ja • OpenID Connect Core 1.0 | OpenID Foundation - https://openid.net/specs/openid-connect-core-1_0.html 参考資料
  41. 73 実装ガイド • OpenID Connect | Google Identity - https://developers.google.com/identity/openid-connect/openid-connect

    ?hl=ja • OAuth 2.0 Policies | Google for Developers - https://developers.google.com/identity/protocols/oauth2/policies • Laravel Socialite | Readouble - https://readouble.com/laravel/12.x/ja/socialite.html 参考資料