default eye-catch image.

Snowflake SAML2.0 Security Integrationを使用したSP/IdP Initiated SAML Federationと構成の詳細

AWS IAM Identity Center を IdP として使用し、Snowflake SAML2.0 Security Integration を構成する。 構成の際に多くのパラメタの設定が必要だが、設定可能なパラメタの意味について深掘りしてみる。 この記事は自分の学び用なので、事実の確認、説明用画像作成のために生成AIを使用するが、 記事の作成、校正には使用しない。 [arst_toc tag=\"h4\"] SP起点(SP initiated) flow SP側にSSOボタンなどを配置して、SSOボタン押下でSSO認証とSPログインを開始するフロー。 SP,User,IdP間の呼び出しシーケンスは下図の通り。 IdP起点(IdP initiated) flow IdP側にログインボタンを配置して、ログインボタン押下でIdP認証とSPログインを開始するフロー。 SP,User,IdP間の呼び出しシーケンスは下図の通り。 ログアウト SP側のセッションと、IdP側のセッションは独立している。SP起点、IdP起点のいずれにおいても、 基本的には、片方をログアウトしたからといってもう片方が勝手にログアウトしたりしない。 ChromeでSP起点でフェデレーションログインした後、ChromeでSPのセッションをログアウトした場合、 IdP側のセッションはまだ生きているので、Chromeで再度フェデレーションを開始したとき、 IdP側の認証は走らず、SPにログインできる。 ChromeでSP起点でフェデレーションした後、SafariでSP起点でフェデレーションしたとき、 Chromeでログアウトしたとしても、Safariのセッションはログアウトしないため操作を続行できる。 ただし、EntraIDのみ、IdP起点で開始したとき、「グローバルログアウト,シングルログアウト,SLO」が サポートされており、IdPからログアウトすると、全てのセッションからログアウトする。 たしかに、EntraIDが気持ち悪い動作するな、と言う時、これが動いている時がありそう。 セッションタイムアウト SP(Snowflake)のセッションがタイムアウトした場合、ユーザはIdPを介して再度認証が必要。 IdPでCancel操作をすると、そこでセッションを終了できる。 IdPのセッションがタイムアウトした場合、Snowflakeセッションに影響しない。 その時点でアクティブなSP(Snowflake)セッションは生きたままとなる。 識別子優先ログイン 組織ごとにそれぞれのIdPとSAML連携したいといった場合がある。複数のintegrationを持てる。 また、ユーザによってはフェデレーション連携させずに、Snowflake認証だけにしたいケースもある。 ユーザによって認証に必要な入力が異なるため、全てのユーザに対して最小公倍数的に入力項目を 出してしまうと、ユーザによって不要な項目が並んでいるように見える。 識別子優先ログインをONにすると、SP(Snowflake)側の認証入力が多段階となる。 つまり、1段階目で識別子(ユーザ名、またはメールアドレス) を入力させ、 入力された識別子がどの認証方式に紐づいているかを判定したのち、 ユーザに適した2段階目の認証入力画面(PW入力、SSOボタン)を表示する。 画面遷移が増えるが、不要な入力項目が現れなくなる。 SAML2.0 Security Integration これを作るだけでSnowflakeにSAML2.0 Federationを追加できる。 CREATE [ OR REPLACE ] SECURITY INTEGRATION [ IF NOT EXISTS ] <name> TYPE = SAML2 ENABLED = { TRUE | FALSE } { METADATA_URL = \'<string_literal>\' | <idp_parameters> } [ ALLOWED_USER_DOMAINS = ( \'<string_literal>\' [ , \'<string_literal>\' , ... ] ) ] [ ALLOWED_EMAIL_PATTERNS = ( \'<string_literal>\' [ , \'<string_literal>\' , ... ] ) ] [ SAML2_SP_INITIATED_LOGIN_PAGE_LABEL = \'<string_literal>\' ] [ SAML2_ENABLE_SP_INITIATED = TRUE | FALSE ] [ SAML2_SNOWFLAKE_X509_CERT = \'<string_literal>\' ] [ SAML2_SIGN_REQUEST = TRUE | FALSE ] [ SAML2_REQUESTED_NAMEID_FORMAT = \'<string_literal>\' ] [ SAML2_POST_LOGOUT_REDIRECT_URL = \'<string_literal>\' ] [ SAML2_FORCE_AUTHN = TRUE | FALSE ] [ SAML2_SNOWFLAKE_ISSUER_URL = \'<string_literal>\' ] [ SAML2_SNOWFLAKE_ACS_URL = \'<string_literal>\' ] [ COMMENT = \'<string_literal>\' ] パラメタの意味を1つずつ解釈していく。 METADATA_URLとidp_parameters まず、METADATA_URL について、IdPが証明書の更新などのIdP構成設定を動的に 取得・同期できるように、URLを公開している場合がある。 その場合、METADATA_URL に指定することができる。ない場合は idp_parameters を手動で設定する。 idp_parameters として設定できる項目は以下の通り。 SAML2_ISSUER IdPメタデータの entityID(IdP Entity ID)。IdP(AWS IAM Identity Center)を一意に識別する文字列。Snowflakeはこの値をIdPからの応答(SAML Response)の Issuer と照合し、正規のIdPからの応答かを検証する。 SAML2_SSO_URL IdPメタデータの SingleSignOnService Location。SP-initiatedログイン時、SnowflakeがブラウザをリダイレクトさせるIdPのログインエンドポイント。 SAML2_PROVIDER IdPの種別。OKTA、ADFS、Customのいずれか。専用値がないIdP(AWS IAM Identity Centerを含む)はすべて Custom を指定する。 SAML2_X509_CERT IdPメタデータの X509Certificate。IdPがSAML Responseに付与する署名を検証するための公開鍵証明書。ヘッダー(-----BEGIN CERTIFICATE-----)・フッター・改行を除いたBase64本文のみを指定する。 多層防御 ALLOWED_USER_DOMAINS SAML2セキュリティ統合で認証できるメールドメインの許可リスト。IdPから返ってきた識別子(login_name/emailとして扱われる値)の @ 以降がこのリストに含まれていないと、SAMLレスポンス自体は正当でも認証を拒否する。IdP設定ミスや、共有IdPで意図しないドメインのユーザーがログインできてしまう事故を防ぐための追加防御層。ただし、NameIDにメールアドレスではなくusernameを使う場合、@domain部分が存在せず、このパラメータで意味のある制限をかけることができない。メールアドレス形式のNameIDに切り替える場合は設定を検討する。 ALLOWED_EMAIL_PATTERNS SAML2セキュリティ統合で認証するメールアドレスが一致すべき正規表現のリスト。ALLOWED_USER_DOMAINS のドメイン単位の許可リストより柔軟な条件(例: .*@example.com$ や特定のローカルパートのみ許可など)を指定したい場合に使う。こちらも同様に、NameID(メールアドレス形式ではない)には適用できない。 SP_INITIATED flowの設定 SAML2_SP_INITIATED_LOGIN_PAGE_LABEL Snowflakeのログイン画面の「Log In With」ボタンの後に表示するラベル文字列。任意の名称でよい。 SAML2_ENABLE_SP_INITIATED ログインページに「Log In With」ボタンを表示するかどうか。TRUEでSP-initiatedログインが有効になる。FALSEの場合はIdP-initiatedのみ許可。 SAMLメッセージの署名 SAML2_SIGN_REQUEST SnowflakeがIdPに送るSAMLリクエスト(AuthnRequest)に署名するかどうか。署名させたい場合は TRUE にし、AWS側アプリ設定でリクエスト署名検証を有効化する必要がある。TRUEにするメリット: IdPからSPへの応答(SAMLレスポンス)はSAML2_X509_CERTによる署名検証で既に真正性が保証されているが、逆方向(SPからIdPへのAuthnRequest)は署名なしだと改ざん・なりすましを検知できない。TRUEにすることでIdP側がリクエストの送信元(Snowflake)と内容の完全性を検証できるようになり、偽装されたAuthnRequestでユーザーを不正なフローに誘導される攻撃への耐性が上がる。特にセキュリティ要件が厳しい環境や監査対応が必要な場合はTRUEを推奨。 SAML2_SNOWFLAKE_X509_CERT 指定しない場合Snowflakeが自己署名した証明書が使われるが、SAML2_SNOWFLAKE_X509_CERT により、第三者(CA)による証明書を設定できる。これにより、SPからIdPへのAuthnRequestを第三者(CA)による証明書(の秘密鍵)で署名するようになる。 SPからIdPへのAuthnRequestを署名する限界と価値 そもそも「証明書」とは「公開鍵」+「(誰かの)秘密鍵による発行者の署名」という構造となっている。第三者が署名するといっても、その第三者が何者か、についてはより上位の第三者が署名することで証明される。最終的には、ルートCAと呼ばれるトラストストアに登録されている事前登録済みの発行者に突き当たり、ルートCAは自己署名を行っている。ルートCAは特権的に自己署名が信頼される様子。WebサイトのSSL/TLS証明書と検証は、まさにこの証明書チェーンの検証を行なっている。 SAML連携において、SPとIdPはこうしたチェーン検証を行わず「指定された証明書を信頼する」という形で個別に相手の証明書を事前登録するだけなので、CA発行か自己署名かどうかは実質的にセキュリティ上の差を生まない。例えば次のようなシナリオでCA発行証明書が意味を持つ。 既に証明書の発行・更新・監査の仕組みが既にある場合、Snowflake固有の自己署名証明書を個別管理する対象として放置せず、他のシステムと同じ棚卸し、アラート、更新プロセスに組み込める。「Snowflakeだけの特殊な例外」として管理外になるリスクを排除できる。 ISO27001/SOC2などの監査で「証明書は組織の承認されたCAから発行されたものであること」という統制が求められている場合、自己署名証明書だと指摘対象になり得る。CA発行にしておけば、発行記録・Subject検証済みという裏付けを監査人に示せる。 NameID規格化 SAML2_REQUESTED_NAMEID_FORMAT SPからIdP方向の AuthnRequest 送信時、「私(SP)はこの形式のNameIDが欲しい」というIdPへのリクエストを送る。このフォーマットを SAML2_REQUESTED_NAMEID_FORMAT で指定する。 一方、IdPからSP方向の SAML Response受信時、Snowflakeが受け取ったNameIDの値をLOGIN_NAMEと文字列比較する処理において、単に文字列として比較されるだけなので、SAML2_REQUESTED_NAMEID_FORMAT に一致していなくて良い。 ログイン・ログアウト時の挙動 SAML2_POST_LOGOUT_REDIRECT_URL Snowflake Webインターフェースの「Log Out」ボタンをクリックした後にSnowflakeがユーザーをリダイレクトするエンドポイント。未設定の場合はSnowflakeの標準ログイン画面に戻る。IdPのログアウトページや社内ポータルなど任意のURLへ誘導したい場合に使う。ただしこれはSAML Single Logout(SLO、IdP側セッションも含めた一括ログアウト)ではなく、あくまでSnowflake側のログアウト完了後にブラウザを1回リダイレクトさせるだけの機能の様子。 SAML2_FORCE_AUTHN ユーザーが最初の認証フロー中に、Snowflakeへアクセスするための再認証を強制されるかどうか。TRUEにすると、ユーザーがAWSアクセスポータルに既にログイン済み(SSOセッションが生きている)でも、Snowflakeへのログインのたびに毎回IdPでの認証をやり直させる。NameIDの形式に関係なく設定可能。特に理由がなければデフォルトのままでよい。 SAML2_SNOWFLAKE_ISSUER_URL SnowflakeサービスプロバイダーのEntityID/Issuerを明示的に指定するパラメータ。省略時はSnowflakeが https://.snowflakecomputing.com をデフォルトとして自動生成する。DESC SECURITY INTEGRATION では、明示指定の有無にかかわらず現在の値が確認できる。 SAML2_SNOWFLAKE_ACS_URL IdPがSAML認証応答を送り返すSnowflakeのAssertion Consumer Service URLを明示的に指定するパラメータ。省略時はSnowflakeが https://.snowflakecomputing.com/fed/login をデフォルトとして自動生成する。DESC SECURITY INTEGRATION では、明示指定の有無にかかわらず現在の値が確認できる。 おわりに Snowflake SAML2.0 Security Integrationを使用したSP/IdP Initiated SAML Federationと構成について ドキュメントをまとめてみた。諸々煩雑な構成が必要だが、Snowflakeが綺麗にラップしていて、パラメタを理解して 設定するだけで、うまくSAML2.0フェデレーションを構成できることがわかった。

default eye-catch image.

OAuth2 implicit grant flow で FitbitAPI の認証をパスするサンプル

これまで、OAuth2 Authenticate code grant flow で FitbitAPI の認証をパスしてデータを取得する事例を書いてきた。implicit grant flow では認証をパスできないのか調べてみた。 Fitbit API の認証を implicit grant flowでパスする際の特徴は以下の通り。 implicit grant flow でFitbitAPI の認証をパスするには、アプリケーションタイプを Client として登録する必要がある。 access tokenの消費期限はユーザが決定する。 refresh tokenを使ってaccess tokenを更新する場合、ユーザの承認が必要となる。 本エントリにて、implicit grant flow で FitbitAPI の認証する方法を step by step で追ってみる。 Authenticate code grant flow と implicit grant flow の違いについて、↓が参考になりました。What is the difference between the 2 workflows? When to use Authorization Code flow? Authenticate code grant flow Authenticate code grant flow は以下の流れだった。 ClientID, ClientSecret から認可コードを取得する 認可コードと accessToken を交換する accessTokenを取得するためには ClientID, ClientSecret を保持しておく必要があった。 本家から図を転載する。 implicit grant flow 対して、implicit grant flow は以下のような流れとなる。 まず、本家からの図の転載。 まとめると次の通り。 ClientIDを使って accessToken を取得する https://www.fitbit.com/oauth2/authorize?state=fx2D6zGcbNVltC15sQlxh4wP8U8HH53%2B &scope=activity+heartrate+location+profile+settings+sleep+social+weight &response_type=token &client_id=xxxxxx &redirect_uri=http%3A%2F%2Fhoge.com%3A8002%2Ftest.php ブラウザ上で認証画面が表示される。この画面はブラウザである必要があり埋め込んでスルーしたりすることはできない。 許可すると、指定したコールバックURLがパラメータ付きで呼び出される。(当然伏字です) http://hoge.com:8002/test.php#scope=weight+location+social+settings+heartrate+sleep+activity+profile &state=fx2D6zGcbNVltC15sQlxh4wP8U8HH53%252B &user_id=xxxxx &token_type=Bearer &expires_in=69483 &access_token=eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NjY5MzMyMzcsInNjb3BlcyI6InJ3ZWkgc nBybyByaHIgcmxvYyByc2xlIHJzZXQgcmFjdCByc29jIiwic3ViIjoiM1FaVllYIiwiYXVkIjoiMjI3V EZKIiwiaXNzIjoiRml0Yml0IiwidHlwIjoiYWNjZXNzX3Rva2VuIiwiaWF0IjoxNDY2ODYzNzU0fQ.oy 0Px-mEauh5Jgw1yS8PF94U37tUW2Q35fbCHKcDFHU ここで得られたaccess_tokenを使って、データアクセスAPIを呼び出す。その際、クエリに含まれるaccess_tokenをAPIにアクセスする度に利用する。クエリに付けるのではなくBASIC認証のヘッダとして付与する。 URLのクエリからaccess_tokenを取得しBASIC認証で利用する。 GET https://api.fitbit.com/1/user/-/profile.json Authorization: Bearer eyJhbGciOiJIUz******************************* ******BybyByaHIgcmxvYyByc2xlIHJzZXQgcmFjdCByc29jIiwic3ViIjoiM1FaVll YIiwiYXVkIjoiMjI3VEZKIiwiaXNzIjoiRml0Yml0IiwidHlwIjoiYWNjZXNzX3Rva2 V**************************************eLtCF0V6IISPinTxy_ZgCLQl1tB0 rEMeqVk4 access_token を使いまわしていると、いずれ access_token の消費期限に到達する。 消費期限に到達した access_token を使ってデータ取得を行うと、以下のようなレスポンスが返ってくる。 { \"errors\": [ { \"errorType\": \"expired_token\", \"message\": \"Access token expired: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzAzNDM3MzUsInNjb3BlcyI6Indwcm8gd2xvYyB3bnV0IHdzbGUgd3NldCB3aHIgd3dlaSB3YWN0IHdzb2MiLCJzdWIiOiJBQkNERUYiLCJhdWQiOiJJSktMTU4iLCJpc3MiOiJGaXRiaXQiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4iLCJpYXQiOjE0MzAzNDAxMzV9.z0VHrIEzjsBnjiNMBey6wtu26yHTnSWz_qlqoEpUlpc\" } ] } ここで、Authenticate code grant flow と同様に refresh_token を使って access_token を更新する必要があるのだが、ドキュメントに記述があるように、implicit grant flow では refresh_token を取得するために client_id を使って再度認証する必要がある。 client_id はアプリケーションにストアすべきデータではなく、ユーザによる操作が必要。 Unlike the Authorization Code Grant Flow, the refresh tokens are not issued with the Implicit Grant flow. Refreshing a token requires use of the client secret, which cannot safely be stored in distributed application code. When the access token expires, users will need to re-authorize your app. また、以下に記述があるように、implicit grant flow における access_token の消費期限は Authenticate code grant flow のそれよりも長めに設定される。アプリケーション側で予め消費期限を設定できるが、最終的には認証画面においてユーザが消費機嫌を決めること値が決まる。 Access tokens from the Implicit Grant Flow are longer lived than tokens from the Authorization Code Grant flow. Users may specify the lifetime of the access token from the authorization page when an application uses the Implicit Grant flow. The access token lifetime options are 1 day, 1 week, and 30 days. Applications can pre-select a token lifetime option, but the user ultimately decides.

default eye-catch image.

OAuth2 Implicit Grant Flow とセキュリティ

Implicit Grant Flow を「認証」のための方法として使ってはならない、というが、ちょっと不勉強で理解が曖昧だったので、少し深く理解してみることにした。 参考にしたソースは下記記事 The problem with OAuth for Authentication. 単なる OAuth 2.0 を認証に使うと、車が通れるほどのどでかいセキュリティー・ホールができる OAuth2 implicit grant flow 自分が所有する情報に対してアクセスを認めることを認可と呼ぶ。 自分が所有する情報と自分の間に第三者が入らない場合 その鍵を自分が管理することに問題はない アクセスするための鍵を自分自身が管理し、安全が保障されている通信の中で直接鍵を利用できる。 自分が所有する情報と自分の間に第三者(Webシステムやアプリ)が入る場合 Webシステムやアプリに対して自分自身の情報に対するアクセス権を移譲する。 鍵をWebシステムやアプリに渡してしまうと、Webシステムやアプリの脆弱性により鍵そのものが危険にさらされてしまう。 代わりに合鍵(accessToken)を作成し、Webシステムやアプリは合鍵を使って自分自身の情報にアクセスするようにする。 以後、第三者は大元のアクセス権に触れずに、accessToken/refreshTokenを使う。 第三者システムを仮に実装してみると、accessToken/refreshTokenを該当システムのIDに紐付けて保存することで、そのシステム上のログインユーザにリソースオーナーへのアクセス権を付与できる implicit grant flow を認証に使う implicit grant flow を認証に使う、というのは、第三者システムが、リソースオーナーから取得したIDをそのまま第三者システムのIDとして使うことを指す。例えば、GoogleやYahooに保存されたID(emailなど)に対してアクセスを許可するだけで、第三者システムのログインそのものを許可してしまうようなものを指す。 だいたいどんなサービスを作るにしても、最初は知名度が低くて、ユーザ登録などしてくれないことがほとんど。 サービス提供者が考えるのは、GoogleやYahooなどのログインを自システムへのログインに代替できないか、ということ。 自分自身の情報へのアクセスを認めただけなのに、勝手に自分自身のログインであることの証明に使われてしまう、というのが問題。 問題は 第三者システムに悪意はなくて、ただ借りパクしたいだけなら被害はない。 第三者システムが悪い奴で、取得したaccessToken/refreshTokenを使って、本人になりすまして、別の第三者システムにログインしてしまったら...。 別の第三者システム的には、本人からのアクセスなのか、本人になりすましたシステムからのアクセスなのか、区別することができないから、CSRF対策を行って防げる攻撃ではない。

default eye-catch image.

Authenticate code grant flow で FitbitAPI からデータを取得する例

Fitbit APIからデータを取得するためには OAuth2 をパスする必要がある。FitbitAPI は以下の2つのflowをサポートしている。 Authenticate grant flow implicit grant flow ドキュメントでは、サーバサイドコードからFitbitAPIにアクセスする場合、Authenticate grant flow が推奨されている。 このエントリでは、djchen/oauth2 というoauth2クライアントを利用して、Authenticate grant flow をパスするサンプルを作成してみる。 composerによるoauth2クライアント(djchen/oauth2)の導入手順 dev.fitbit.comへのサンプルアプリ登録 FitbitのOAuth2認証をパスするためのサンプル実装 Fitbitからサンプルデータの取得 composerによるoauth2クライアントの導入手順 phpスクリプトをブラウザから叩ける環境を用意する。VagrantでもVPSでも何でも。 ~/wwwが DocumentRoot となるように httpd を構成する。 $ pwd /home/ikuty/www 次に、composerを使ってOAuth2クライアントをインストールする。 $ composer require league/oauth2-client djchen/oauth2-fitbit dev.fitbit.comへのサンプルアプリ登録 これから作るサンプルアプリを dev.fitbit.com に登録する必要がある。 例えば以下のような登録を行う。 Application Name: Test Application Description: This is my first test. Application Web Site: http://hoge.com:8001 Organization: Personal Organization Web Site: https://ikuty.com/ OAuth2.0 Application Type: Personal Callback URL: http://hoge.com:8001/test.php Default Data Access: readonly すると、以下を発行してもらえる。 OAUth 2.0 Client ID Client Secret OAuth 2.0: Authorization URI OAUth 2.0: Access/Refresh Token Request URI FitbitのOAuth2認証をパスするためのサンプル実装 サンプル実装、といっても本家のサンプルをそのまま流用しただけだが…。 以下の Authenticate grant flow の流れの通りとなっている。 ClientID,ClientSecretを使用して認証コードを取得する 認証コード を accessToken に変換する accessToken の有効期限に達したら refreshToken を使って accessToken を更新する $ pwd /home/ikuty/www $ vi test.php 中身は以下 <?php require_once \'./vendor/autoload.php\'; use djchenOAuth2ClientProviderFitbit; use LeagueOAuth2ClientTokenAccessToken; $provider = new Fitbit([ \'clientId\' => \'{client_id}\', // 登録時に取得したclientId \'clientSecret\' => \'{clientSecret}\', //登録時に取得したclientSecret \'redirectUri\' => \'http://hoge.com:8002/test.php\' ]); session_start(); if(!isset($_GET[\'code\'])){ $authorizationUrl = $provider->getAuthorizationUrl(); $_SESSION[\'oauth2state\'] = $provider->getState(); header(\'Location: \'.$authorizationUrl); exit; } elseif ( empty($_GET[\'state\']) || ($_GET[\'state\'] != $_SESSION[\'oauth2state\'])){ unset($_SESSION[\'oauth2state\']); exit(\'Invalid state\'); } else { try { $forceToAuth = false; $needToRewrite = false; $lines = file(\'token.txt\',FILE_IGNORE_NEW_LINES); if (($lines == false) || (count($lines) == 0) || $forceToAuth) { echo \'authorization_code->\'; //ここで 認証コード と accessToken を交換する $accessToken = $provider->getAccessToken(\'authorization_code\',[\'code\'=>$_GET[\'code\']]); $needToRewrite = true; } else { echo \'existing AccessToken->\'; $_accessToken = $lines[0]; $_refreshToken = $lines[1]; $_expiredToken = $lines[2]; $accessToken = new AccessToken([\'access_token\'=>$_accessToken, \'refresh_token\'=>$_refreshToken, \'expires_in\'=>$_expiredToken]); // accessToken の有効期限に達したら refreshToken を使って新しい accessTokenを要求する if ($accessToken->hasExpired()) { echo \'refresh AccessToken->\'; $refreshToken = $accessToken->getRefreshToken(); $accessToken = $provider->getAccessToken(\'refresh_token\',[\'refresh_token\'=>$refreshToken]); $needToRewrite = true; } } if ($needToRewrite) { $file = fopen(\"token.txt\",\"wb\"); fputs($file, $accessToken->getToken()); fputs($file, \"n\"); fputs($file, $accessToken->getRefreshToken()); fputs($file, \"n\"); fputs($file, $accessToken->getExpires()); fputs($file, \"n\"); fputs($file, $accessToken->getResourceOwnerId()); fputs($file, \"n\"); fclose($file); } } catch (Exception $e){ } } 最初、「$accessToken->getToken()を保存しておいて次回利用時に使いまわす」具体的な方法が分からなかった。oauth2-client の AccessTokenクラスの実装を見ると、コンストラクタにアクセストークン等を渡してあげればインスタンス化できることがわかった。 アクセストークンの有効期限に達すると、hasExpired()メソッドがtrueを返すようになる。その場合、リフレッシュトークンを使って新しいアクセストークンを要求する。DBに保存しておいたアクセストークンを新しい値で上書きする。 Fitbitからサンプルデータの取得 RESTfulなAPIを指定することでデータが得られる。例えばユーザのプロフィールを取得するには以下の通りとする。厳密にAPIにパラメータを全て埋め込むタイプではなくログイン情報等のセッション情報も用いられるタイプ。以下では、user-idとして\"-\"を渡すとセッションにあるユーザIDが使われるようだ。 $request = $provider->getAuthenticatedRequest( \'GET\', Fitbit::BASE_FITBIT_API_URL . \'/1/user/-/profile.json\', $accessToken, [\'headers\' => [\'Accept-Language\' => \'ja_JP\'],[\'Accept-Locale\' => \'ja_JP\']] ); $response = $provider->getResponse($request); var_dump($response);