🔑

OAuth2.0を理解したいのでGASで実装してみた

に公開

はじめに

OAuth2.0について理解をした方がよいという話を伺ったのでまとめていこうと思います。

私は例えば自作アプリでは認証機能をSupabaseに一任していました。その時はsupabase.auth.signInWithOAuth()というコード一つでできました。

この裏で何が行われていたのか、それを理解していこうと思います。

なぜOAuth2.0が使われるのか

そもそもどうしてこの仕組みが生まれたのでしょうか。
例えば、記録アプリの記録をtwitterに投稿したいとします。従来の認証方法では、記録アプリがユーザーのtwitterのログイン情報を保持し、それを利用して認証を突破し投稿する必要がありました。この方式にはいくつか問題がありました。

  1. 記録アプリがtwitterパスワードを平文で保存している
  2. twitter側はパスワード方式が脆弱であってもパスワード認証をサポートする必要がある
  3. アクセス権を変更することが難しい

これをOAuth2.0では、認証に加えて認可という段階を作り、記録アプリはパスワードを保持せず、twitter経由でユーザーに「このような権限が欲しいんですけどいいですか?」と認可を求め、ユーザーが認可すればそのとき発行されるアクセストークンを用いてアクセスするようにしました。

ここで 認証 ・ 認可という二つの用語について整理します

  • 認証:
    ユーザーが誰であるかを確認します。
    ここではtwitterにログインすることを指します。

  • 認可:
    ユーザーが、どのアプリや情報にアクセス出来るかを決めます。
    ここでは記録アプリにtwitterで投稿する許可を与えることです

OAuth2.0 の大まかな流れ

具体的にどのような手順で「認可」が行われるのか、今回の実装対象である「認可コードフロー」を図解します。

Supabaseは何をしてくれていたのか

Supabaseなどのサービスが上で説明した面倒なフローのうち以下のことを勝手にやってくれていました。

  1. 認可URLの生成: client_id や scope を含んだ正しいURLを勝手に作ってリダイレクトしてくれる。
  2. コールバックの受け取り: 認証後のリダイレクト先サーバーを自動で用意してくれ、認可コードを受け取ってくれる。
  3. トークンの交換: 認可コードをアクセストークンに交換するPOSTリクエストを勝手に送信してくれる。
  4. トークンの保存: 取得したトークンをブラウザのローカルストレージやCookieに安全に保存してくれる。
  5. リフレッシュ処理: トークンの期限が切れる前に、勝手に新しいトークンを取り直してくれる

GASでの実装

アクセストークンの取得をGASを用いて実装してみました。Node.jsなどの記事では form プロパティを使う例が多いですがGASのUrlFetchAppではpayloadプロパティを使うことに注意します。

function getAccessToken() {
  const token_url = "https://example.com/oauth/token";
  const code = '取得した認可コード'; 
  const client_id = 'Client ID'; // 会員番号
  const client_secret = 'Client Secret'; // パスワード
  // 認証時と同じURLである必要がある(GASなら手動取得時はoobを使うことが多い)
  const redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'; // 認可後に戻る先

  const options = {
    method: 'post',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded' 
      // これがあることで、payloadプロバティの中身をOAuth2.0が要求する形式に変形してくれる
    },
    payload: {
      grant_type: "authorization_code",
      redirect_uri: redirect_uri,
      client_id: client_id,
      client_secret: client_secret,
      code: code
    },
    muteHttpExceptions: true
  };

  const response = UrlFetchApp.fetch(token_url, options);
  console.log(response.getContentText()); 
}

2種類のトークン

先ほどまでアクセストークンのみについて説明をしました。アクセストークンには非常に短い有効期限が設定されています。ここで使われるのがリフレッシュトークンというものです。アクセストークンの再発行に利用できる有効期限が長いトークンで、これがあることで何度も認可をする必要がなくなり、ユーザーにとって使いやすくなります。

GASでリフレッシュトークンを使って新しいアクセストークンを取得する場合、grant_type の指定方法が少し変わります。

function refreshAccessToken(refresh_token) {
  // ...URLなどは同じ...
  const options = {
    method: 'post',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    payload: {
      grant_type: "refresh_token", // ここは固定文字列
      refresh_token: refresh_token, // ここに保存しておいたトークンを入れる
      client_id: client_id,
      client_secret: client_secret,
    }
  };
  // ...fetch処理...
}

まとめ

今回実装をしてみたことで、今まで見ないことにしていた認証の仕組みがしっかり理解できました。

参考文献

https://openid-foundation-japan.github.io/rfc6749.ja.html

https://techblog.asia-quest.jp/202410/what-is-oauth-2.0-authentication-and-authorization

Discussion