問い合わFastly 無料トライアル

エッジでの認証処理をよりシンプルにする OAuth

認証とは複雑で危険なプロセスでもありますが、必要不可欠なものです。 アプリのほとんどにおいて認証処理が必要としているため、ほぼ全てのエンドユーザーリクエストの前提条件になっています。Web アプリの認証は、エンドユーザーの近くで行われると同時に、システムの他の部分からは隔離されていること理想的です。またセキュリティー担当者によって実装維持され、統合が簡単であることが重要です。

Fastly Compute@Edge実装されている高速、安全、自律的、そして分散されている OAuth サービスは、まさに理想的な認証です。今回は、その手順について説明します。

simplified flow diagram

まずは認証と認可の違いですが、認証 (authentification) とは相手が誰であるか証明することであり、認可 (authorization)誰に何の権限を与えるかという判断です。では、まずユーザー ID確立する方法に注目しましょう。ユーザー ID確立するには、OAuth 2.0 OpenID Connect使うのが一般的です。

エッジに届いたリクエストは、特定のユーザーに関連づけられるものと、匿名または無効なものとを区別する必要があります。匿名のリクエストは、OAuth 認可コードのフローを辿って処理され、無効なリクエストは拒否されます。その結果、認証されたユーザーからのリクエストのみが、アプリケーションオリジンサーバーに進むことができます。

これが手順を詳し表すフローです。

flow diagram

では、順番に見ていきましょう。

  1. ユーザーは、保護されているリソースへのリクエストを行いますが、セッション Cookie持っていません。

  2. そこで Fastlyエッジで以下を生成します。

    1. ユーザーが行おうとしていたアクション /articles/kittens読み込み)エンコードする、固有で推測不可能な state パラメーター。

    2. Code verifier呼ばれる、暗号化されたランダムな文字列。

    3. Code verifier から導出された code challenge

    4. State nonce (リプレイ攻撃を軽減するために使用される固有の値)エンコードする、シークレットを用いて認証された期限付きトークン。

    (a) (b) は、後で検証できるようにCookie保存します。(c) (d) は、認可サーバーへの次のリクエストに含まれます。

  3. Fastly認可 URL構築し、ID プロバイダーが運行する認可サーバーにユーザーをリダイレクトします。

  4. ユーザーは ID プロバイダー (IdP) でのログイン手続きを直接完了させます。 ログイン処理の結果を含むコールバック URL へのリクエストを受信するまで、Fastlyこれには関与しません。IdP は、ログイン後のコールバックに認可コード state (先ほど作成した期限付きトークンと一致するもの)含め返します。

  5. エッジサービスは、IdP からの state トークンを認証し、保存している state 値がそのサブジェクトクレームと一致することを確認します。

  6. Fastly ID プロバイダーに直接接続し、認可コード (1回の使用可) code verifier以下のセキュリティトークンと交換します。

    1. アクセストークン : ユーザーの代わりに特定の操作を実行する権限を表すキー

    2. ID トークン : ユーザーのプロファイル情報が含まれるトークン

  7. Fastlyは、Cookie保存されているセキュリティトークンと共に、エンドユーザーを元のリクエスト URL /articles/kittensリダイレクトします。

  8. ユーザーがリダイレクトされたリクエスト、またはセキュリティトークンを伴うリクエストを行うと、Fastly双方のトークンの整合性、有効性、およびクレームを検証します。トークンに問題な場合は、リクエストをオリジンにプロキシします。

さて、この手順を構築するには、まずは ID プロバイダーが必要です。

ID プロバイダー (IdP)取得

独自の ID サービスを利用することも可能ですが、OAuth 2.0 OpenID Connect (OIDC)準拠したプロバイダーであれば何でも構いません。以下の手順で IdP設定していきます。

  1. まずアプリケーションを ID プロバイダーに登録します。Client_id認可サーバーアドレスを忘れないようにしてください。

  2. サーバーに関連付けられた OpenID Connect Discovery メタデータローカルコピーを保存します。これは、認可サーバーのドメインの /.well-known/openid-configurationあります。例えば、こちらが Google提供しているものです

  3. JSON Web Key Set (JWKS) メタデータのローカルコピーを保存します。これは、先ほどダウンロードした Discovery メタデータドキュメント内の jwks_uri プロパティ下にあります。

次に、IdP通信する Compute@Edge サービスを Fastly 上で作成します。

Compute@Edge サービスの作成

このプロジェクトに必要なものはすべて GitHubリポジトリにまとめましたが、それ以外にも Compute@Edge サービスを利用可能な Fastly アカウントが必要になります。まだインストールを行っていな場合は、Compute@Edge ウェルカムガイド従い、Fastly CLI Rust ツールチェーンをローカルマシンにインストールしてください。準備が整ったら、早速コーディングを始めましょう。

  1. まずリポジトリをクローンしてください : git clone https://github.com/fastly/compute-rust-auth

  2. README記載されている指示に従ってくださ

おめでとうございます!これで Compute@Edge サービスのデプロイが完了しました。統合を完了するには、ご利用の ID プロバイダーが、ログイン後にユーザーを Compute@Edge サービスに誘導するように設定されているか確認してください。

ID プロバイダーをリンクする

https://{some-funky-words}.Edgecompute.app/callback を、ID プロバイダーのアプリ設定内の許可されたコールバック URL リストに追加します。これにより、認可サーバーはユーザーを Fastlyホストする Web サイトに送り返すことができます。

では、早速ブラウザでアプリを開いてみましょう。

Auth0 screenshot

このサンプルアプリでは、パスにアクセスするために認証が必要になります。すでに認証されている場合、Fastly通常どおりリクエストをオリジンにプロキシします。より野心的なことも実行可能ですが、それについては後日のブログ記事で詳し説明したいと思います。今回の記事では、この基本的な統合の機能を紹介します。

Compute@Edge との統合

Fastly Rust SDK用いて Rust書かれた Compute@Edge プログラムの main 関数は、リクエストのエントリーポイントとして機能しています。通常、main 関数は Request 構造体を受け取り、Response 構造体を返します。

まず main 関数では、ユーザーが ID プロバイダーから戻り、セッションを初期化する準備ができていることを示すコールバックパス処理しているかどうか確認します。このパスは常に阻止される必要があり、バックエンドにプロキシされることはありません。

if req.get_url_str().starts_with(&redirect_uri) {
// ... snip: Validate state and code_verifier ...
// ... snip: Check authorization code with identity provider ...
// ... snip: Identity provider returns access & ID tokens ...
Ok(responses::temporary_redirect(
        original_req,
        cookies::session("access_token", &auth.access_token),
        cookies::session("id_token", &auth.id_token),
        cookies::expired("code_verifier"),
        cookies::expired("state"),
    ))
}

ID プロバイダーからのレスポンスを受け取り、認可コードおよび PKCE使い (先ほどの code verifier challenge出番です)、access_token および id_tokenID取得します。

  • このアクセストークンはベアラートークンです。つまり、持参人がユーザーに代わって認証済みリソースにアクセスする権限があることを意味します。 

  • ID トークンは、ユーザーの ID 情報をエンコードした JSON Web Token (JWT) です。

将来のリクエスト認証に使用するため、これらを Cookie保存し、認証プロセスの途中で使用した Cookie破棄します (先ほどの state パラメーターと code verifier出番です)。そして、ユーザーを最初に希望していた URL へとリダイレクトします。

ユーザーがコールバックパス上にいないこと確認できた場合は、リクエストに付随する Cookieチェックして、アクティブセッションがあるかどうか判断します (アクセストークンと ID トークンによって定義されています) 。

let cookie = cookies::parse(req.get_header_str("cookie").unwrap_or(""));
if let (Some(access_token), Some(id_token)) = (cookie.get("access_token"), cookie.get("id_token")) {
    // snip: ... validation logic ...
    req.set_header("access-token", access_token);
    req.set_header("id-token", id_token);
    return Ok(req.send("backend")?);
}

有効なセッションが存在する場合は、req.send()リクエストをアップストリームのオリジンに送信し、ダウンストリームのクライアントに送信する Response返します。今回のサンプルサービスでは、アクセストークンと ID トークンをオリジンへのカスタム HTTP ヘッダーに設定しています。オリジンで IDどのように使用するかによって他の方法もありますが、それについてはまた後ほど説明します。

最後に、ユーザーが認証されておらず、ログイン中でもな場合は、ユーザーを ID プロバイダーに送信してサインインプロセスを開始します。

let authorize_req =
Request::get(settings.openid_configuration.authorization_endpoint)
  .with_query(&AuthCodePayload {
     client_id: &settings.config.client_id,
      code_challenge: &pkce.code_challenge,
      code_challenge_method: &settings.config.code_challenge_method,
      redirect_uri: &redirect_uri,
      response_type: "code",
      scope: &settings.config.scope,
      state: &state_and_nonce,
      nonce: &nonce,
  })
  .unwrap();
Ok(responses::temporary_redirect(
    authorize_req.get_url_str(),
    cookies::expired("access_token"),
    cookies::expired("id_token"),
    cookies::session("code_verifier", &pkce.code_verifier),
    cookies::session("state", &state),
))

ここでは Request::get使ってリクエストを作成していますが、それを送信する代わりに、シリアル化された URL抽出し、ユーザーをそちらにリダイレクトしています。また、後から検証できるように state パラメーターと code verifier保存します。以前のコードでは承認されなかったことが明らかなため、アクセストークンと ID トークンは削除します。

以上が、認証ソリューションと受信リクエスト間にあり得る3つのシナリオです。

最後に

認証を行った上で、ID データを使用して認可決定を行うという基本的なプロセスは、Web アプリやネイティブアプリの大半に適用されるものです。これをエッジで行うことで、開発者とエンドユーザー両方にとって非常に大きなメリットが得られます。

  • セキュリティの向上 : 認証プロセスは、すべてのバックエンドアプリケーションに適用される単一のユニバーサルな実装であるため、セキュリティが向上します。

  • メンテナンス性の向上 : コンポーネントが切り離されているため、メンテナンスが行いやすくなります。

  • プライバシー配慮 : ユーザーデータを必要な場所に保管し、オリジンアプリとのデータ共有を最小限に抑えることができるため、プライバシーへの配慮が高まります。

  • パフォーマンスの向上 : アクセス認証が必要なコンテンツをエッジでキャッシュすることができ、認証プロセスに関連するリクエストにエッジで直接応答することができるため、パフォーマンスが向上します。

これはエッジコンピューティングの数多くある使用例の1つであり、コアインフラから state遠ざける良い例でもあります。分散システムにおけるスケーラビリティの課題は、state をより短期間で非同期にすることに関係しています。先日 Andrew公開したエッジネイティブアプリについてのブログ記事も、併せてご覧ください。

皆さんがどのように Compute@Edge活用していくのか、とても楽しみにしています。

Dora Militaru
Developer Relations Engineer
Andrew Betts
Head of Developer Relations
投稿日
興味がおありですか?
エキスパートへのお問い合わせ
この投稿を共有する
Dora Militaru
Developer Relations Engineer

Fastly Developer Relations Engineer 務める Dora グローバル規模のニュースサイトの開発者として経験を積みました。また Dora データ保護チームやサイト信頼性エンジニアリングチームを指導した経験もあり、常に思いやりのある開発を心がけています。ロンドンのキッチンの片隅から、より信頼性の高い、よりスピーディで優れたインターネットの構築を支えています。

Andrew Betts
Head of Developer Relations

Andrew Betts Fastly Head of Developer Relations として世界各地の開発者と協力し、Web 高速化、セキュリティ、信頼性、使いやすさの向上に努めています。Fastly 入社前は、Web コンサルティング会社 (後に Financial Times により買収) 設立し、Financial Times 先駆的な HTML5 ベースの Web アプリケーションの開発を統括したほか、同紙のラボ部門の設立にも携わりました。また、W3C Technical Architecture Group (World Wide Web 開発を導く9名で構成される委員会) 選出メンバーでもあります。