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

PHP で学ぶ OAuth 入門

Avatar for SAW SAW
April 12, 2025

PHP で学ぶ OAuth 入門

PHPカンファレンス小田原2025 の発表資料です。

Avatar for SAW

SAW

April 12, 2025
Tweet

More Decks by SAW

Other Decks in Programming

Transcript

  1. $(whoami) w ࢯ໊Ճ౻फҰ࿠ ࡀ  w ϋϯυϧωʔϜ4"8 w 9 چ5XJUUFS

    !B[VLJ@FBUFS w ؔ੢ͷ*5ΤϯδχΞίϛϡχςΟͷ೐΍͔͠ ࣗশ  w େࡕࡏॅɾѪ஌ग़਎ w ಘҙ෼໺8FCΞϓϦέʔγϣϯ։ൃ w -BSBWFM 7VF w ॴଐ༗ݶձࣾΞϦ΢ʔϓ 2 چ+*4ϚʔΫ ⁴ ͕ ਓͷԣإʹݟ͑ͨͷ͸ ࣗ෼͚ͩͰ͸ͳ͍͸ͣ ࠓ೔ͷ໎ݴ
  2. ඵએ఻ w 1)1ΧϯϑΝϨϯεؔ੢ਆށͰ։࠵ w ೔෇ ۚ ɾ ౔  w

    ։࠵৔ॴਆށӺલݚमηϯλʔ w ઈࢍϓϩϙʔβϧืूத w క੾ ೔ ·Ͱ w εϙϯαʔ΋ืूத 3 ϓϩϙʔβϧืूϑΥʔϜ εϙϯαʔืूࢿྉ
  3. ͜ͷτʔΫͷ֓ཁͱ໨ඪ w ೝՄٕज़ͷͭͰ͋Δ0"VUIͷجૅΛઆ໌ w ೝূɾೝՄͷجૅ w 0"VUIͱ͸Կ͔ w 0"VUIͷ࢖͍Ͳ͜Ζ w

    ؆୯ͳ࣮૷ྫΛަ͑ͨ0"VUIͷೝՄॲཧͷઆ໌ w ໨ඪ0"VUIͷجૅతͳॲཧͷྲྀΕΛ௫Ή w ʮখాݪͰΈΜͳͰֶ΅͏0"VUIʯˠʮখాݪͰجૅΛֶΜͩ0"VUIʯ 4
  4. ೝূͱೝՄ w ೝূ "VUIFOUJDBUJPO  w γεςϜʹొ࿥͞Ε͍ͯΔϢʔβʔͷຊਓΛ֬ೝ͢Δॲཧ w ྫ4/4΍&$αΠτͳͲͷ8FCγεςϜͷϩάΠϯ w

    γεςϜʹొ࿥͞Ε͍ͯΔϢʔβʔ৘ใͱ߹க͢Δ͜ͱͰγεςϜ͕ར༻Ͱ͖Δ w ೝՄ "VUIPSJ[BUJPO  w γεςϜͷػೳ΍Ϧιʔε΁ͷΞΫηεͷՄ൱Λ੍ޚ͢Δॲཧ w ྫ6/*9ͷϑΝΠϧγεςϜͷύʔϛογϣϯ w ϑΝΠϧ΁ͷૢ࡞͕ͦͷϢʔβʔʹରͯ͠ڐՄ͞Ε͍ͯΔ৔߹ʹͷΈૢ࡞͕Մೳ 8
  5. 0"VUIͷར༻ྫ w ESBXJP͔Β(PPHMF%SJWFʹΞΫηε w ESBXJP8FC্Ͱ࡞ਤͰ͖ΔαʔϏε w (PPHMF%SJWF(PPHMF੡ͷΫϥ΢υετϨʔδαʔϏε w ESBXJP͕(PPHMF%SJWFʹٻΊΔΞΫηεݖͷྫ w

    ετϨʔδ಺ͷϑΝΠϧͷಡΈࠐΈ w ετϨʔδ΁ͷϑΝΠϧͷอଘ 10 ESBXJP͔Β(PPHMF%SJWF΁ͷ ΞΫηεͷೝՄΛཁٻ (PPHMF%SJWFͰΞΫηεݖͷೝՄΛ֬ೝ
  6. 0"VUIʹ͓͚Δొ৔ਓ෺ w ΫϥΠΞϯτ $MJFOU  w อޢର৅ϦιʔεʹΞΫηε͢ΔSEύʔςΟ੡ͷιϑτ΢ΣΞ w อޢର৅Ϧιʔε 1SPUFDUFE3FTPVSDF

     w Ϧιʔεॴ༗ऀ͕ΞΫηεݖΛ࣋ͭػೳ΍σʔλͳͲͷϦιʔε w Ϧιʔεॴ༗ऀ 3FTPVSDF0XOFS  w อޢର৅Ϧιʔε΁ͷΞΫηεݖΛ΋ͭϢʔβʔ w ೝՄαʔόʔ "VUIPSJ[BUJPO4FSWFS  w Ϧιʔεॴ༗ऀ͕ΫϥΠΞϯτΛೝՄ͢Δ࢓૊ΈΛఏڙ͢ΔγεςϜ 13
  7. ΫϥΠΞϯτͷೝՄཁٻ w Ϧιʔεॴ༗ऀΛೝՄαʔόʔʹϦμΠϨΫτ w ϦμΠϨΫτ࣌ʹೝՄର৅ͷΫϥΠΞϯτͷ৘ใΛΫΤϦύϥϝʔλʹ෇༩ 30 $query = http_build_query([ 'response_type'

    => 'code', 'client_id' => env('CLIENT_ID'), 'redirect_uri' => 'http://localhost:8000/callback', 'scope' => 'file.read file.write', ]); $authEndpoint = Uri::new('http://localhost:8001/authorize') ->withQuery($query); return redirect()->away($authEndpoint); ೝՄαʔόʔ Ϧιʔεॴ༗ऀ ΫϥΠΞϯτ ೝՄαʔόʔʹ ϦμΠϨΫτ localhost:8000 localhost:8001 ෇༩ํࣜ͸BVUIPSJ[BUJPODPEF ΫϥΠΞϯτ͕ཁٻ͢Δείʔϓ
  8. ΫϥΠΞϯτͷೝՄͷ֬ೝ w ΫϥΠΞϯτΛೝՄ͢Δ͔Ϧιʔεॴ༗ऀʹ֬ೝ w είʔϓ͸ۭനจࣈ۠੾ΓͰΫΤϦύϥϝʔλͰ౉͞ΕΔ 31 $clientId = $request->query('client_id'); $client

    = Client::find($clientId); // 要求されているスコープを取得 $scope = str($request->query('scope')) ->explode(' ')->filter(); return view('authorize', [ 'client' => $client, 'scope' => $scope, ]); ೝՄαʔόʔ Ϧιʔεॴ༗ऀ ΫϥΠΞϯτͷ ೝՄΛ֬ೝ localhost:8001
  9. ΫϥΠΞϯτͷೝՄ w ೝՄίʔυΛൃߦͯ͠ΫϥΠΞϯτͷίʔϧόοΫ63-ʹϦμΠϨΫτ w ೝՄίʔυͱϦιʔεॴ༗ऀ͕ڐՄͨ͠είʔϓΛΫΤϦύϥϝʔλͱͯ͠෇༩ 32 $scope = $request->input('scope'); $authzCode

    = Str::random(); $codes[$authzCode] = ['scope' => $scope]; Cache::put('codes', $codes); $callbackUrl = Uri::new($redirectUrl) ->withQuery([ 'scope' => $scope, 'code' => $authzCode, ]); return redirect()->away($callbackUrl); ೝՄαʔόʔ Ϧιʔεॴ༗ऀ ΫϥΠΞϯτ ΫϥΠΞϯτʹ ϦμΠϨΫτ localhost:8000 localhost:8001 ೝՄίʔυΛ෇༩
  10. τʔΫϯͷऔಘ w ΫϥΠΞϯτ͸ೝՄαʔόʔʹೝՄίʔυΛૹ৴ w ΫϥΠΞϯτ͸#BTJDೝূͰࣝผ w ೝՄαʔόʔ͸ΫϥΠΞϯτʹΞΫηετʔΫϯΛൃߦ 33 ೝՄαʔόʔ ΫϥΠΞϯτ

    ೝূ৘ใͱೝՄίʔυΛ ૹ৴ localhost:8000 localhost:8001 $clientId = $request->getUser(); $clientSecret = $request->getPassword(); $codes = Cache::get('codes'); $code = $codes[$request->input('code')]; // クライアントの情報と認可コードを検証 $token = Str::random(); AccessToken::create(['token' => $token]); return response()->json([ 'access_token' => $token, 'token_type' => 'Bearer', 'scope' => $code['scope'], ]); ΞΫηετʔΫϯΛૹ৴ #BTJDೝূͰΫϥΠΞϯτΛࣝผ
  11. อޢର৅Ϧιʔε΁ͷΞΫηε w ΫϥΠΞϯτ͸อޢର৅ϦιʔεʹΞΫηετʔΫϯΛར༻ͯ͠ΞΫηε w ΞΫηετʔΫϯ͸#FBSFSτʔΫϯͰ౉͞ΕΔ w อޢର৅Ϧιʔε͸ϦιʔεΛΫϥΠΞϯτʹฦ٫ w ೝՄ͞Εͨείʔϓ֎ͷΞΫηε͸ڐՄ͠ͳ͍ 34

    ΫϥΠΞϯτ ΞΫηετʔΫϯΛ෇༩ͯ͠ ϦιʔεʹΞΫηε localhost:8000 localhost:8002 Ϧιʔεͷ಺༰Λฦ٫ อޢର৅Ϧιʔε $token = $request->bearerToken(); $accessToken = AccessToken::whereFirst('token', $token); if ($accessToken?->scope->contains('file.read')) { $content = Storage::json('data.json'); return response()->json($content); } else { return response()->json(null, 403); } #FBSFSτʔΫϯ͔Β ΞΫηετʔΫϯΛऔಘ ΞΫηετʔΫϯʹཁٻ͞ΕΔ είʔϓ͕ଘࡏ͢Δ͔νΣοΫ