自分は今アプリを開発しているのですが、Googleスプレッドシートへデータに保存したくて、Google OAuth2.0を勉強せざるを得なくなり、他にも困っている人がいるのではないかと思って、記事を書いています。
まぁ、前置きが長くてもらちが開かないので、早速実装に移りたいと思います。
ちなみに、OAuth2.0では、「URI」という表記がよく使われますが、初心者をはじめ大多数の方は、「URL」の方に馴染みがあると思うので、便宜上そちらにあわせようと思います。
そもそもOAuth2.0とは何か
IDとパスワードを使わずにアクセスする仕組み
例えば、僕があるアプリを作ったとします。そのデータをスプレッドシートに保存したいと思った時に、ユーザーからメールアドレスのようなIDやパスワードといった機密情報を直接受け取らなくても、ユーザーのスプレッドシートにアクセスできるようにするための仕組みです。
図を使って説明するとわかりやすいです。まずは、エンドユーザーがスプレッドシートにアクセスする場合を考えてみましょう。
通常、ユーザーが直接スプレッドシートを編集するときは、Googleアカウントのメールアドレスとパスワードを使ってアクセスします。正しい情報を入力するとGoogleのサーバーがスプレッドシートのデータを返してくれます。また、編集するときのデータも反映させてくれます。
これはスプレッドシートに限らず、GoogleドライブやGoogleドキュメントといった他のサービスでも同様です。さらに、Google以外にも「Appleでログイン」といった機能もAppleに紐づけられている情報にアクセスするといった点で共通しています。
アプリやサーバーから、Googleのサービスにアクセスするときも同様に、本来はIDとパスワードが必要なのですが、万が一IDとパスワードがアプリやサーバーから漏れてしまうと、Googleのサービスに不正にアクセスされてしまう危険性が、高くなります。
OAuth2.0はパスワードとIDを使うことなく、アプリやサーバーにGoogleのサービスへのアクセス権を付与することができる、安全で便利な仕組みなのです。
アクセストークンを取得する
「パスワードとIDを使わないなら、どうやってアクセスを許可するの?」と気になる人もいるでしょう。そう思った方、鋭いです!
OAuth2.0ではIDとパスワードの代わりに、「アクセストークン」と呼ばれる文字の羅列を鍵として使い、アクセスを許可してもらいます。サーバーからユーザーが利用するサービスにアクセスする場合、私たちが構築するサーバーも含め、主に3種類のサーバーが関係しています。
1つ目が「クライアントサーバー」というものです。これは、主に私たちが構築するサーバーで、WebアプリやバックエンドAPIなどをホストしているサーバーになります。
2つ目が、「認可サーバー」です。これはユーザーの同意に基づいて、アクセストークンを発行し、クライアントサーバーがのちに紹介する「リソースサーバー」にアクセスするのを許可します。
3つ目が、「リソースサーバー」です。これは、GoogleスプレッドシートやGoogleドライブといった、具体的なユーザーのデータが保存されているサーバーです。
文章だけだとわかりにくいので、以下の図をご覧ください。
OAuth2.0はなかなか複雑ので、上記の図だけで表しきれていない部分もありますが、大体は上記のようなイメージです。
1.まず、ユーザーがバックエンドサーバーに対して、スプレッドシートなどに、データを保存する操作をしたとします。
2.すると、バックエンドサーバーは、まずOAuth2.0認可サーバーに対して、アクセストークンをリクエストします。
3.すると、認可サーバーはバックエンドサーバーに対して、「まずはユーザーの許可をもらってください」と認証URI、つまりログイン画面へのリンクを送ってきます。そこにアクセスすると見慣れたGoogleでログインの画面が現れます。
ここで、「許可」を押すと、認可サーバーに対してサーバーがユーザーのドライブやスプレッドシートにアクセスすることを許可することができます。
4.ユーザーからの許可を得たことを認可サーバーが検知すると、サーバーに対してアクセストークンが送られ、APIをリクエストするときに、そのアクセストークンを一緒にリソースサーバーに送信すると、リソースサーバーがユーザーのデータにアクセスさせてくれるようになります。
と、いうのがOAuth2.0の流れです。複雑ですね(笑)。
次からは、Pythonを使ってアクセストークンを取得して、GoogleのAPIにアクセスをしてみたいと思います。
OAuth2.0を実装してみよう!
コードの全体像
今回紹介するコードの全体像は以下の通りになります。
# OAuth2.0に必要なライブラリです。
import google_auth_oauthlib.flow
from flask import Flask, render_template, session, url_for, redirect, request
import os
"""
表記について
「URI」の名前を持つメソッドが出てくると思いますが、
便宜上「URL」と置き換えます。「URI」と書かれたメソッドが出てくると思いますが、
その際は「URL」と読み替えてください。
"""
"""
クライアントシークレットのパスです。
本番環境では危険なので、環境変数か、.envを使って取得してください。
今回はテスト環境なので、ハードコードします。
例
CLIENT_SECRETS_FILE = os.environ.get("CLIENT_SECRETS_FILE")
"""
CLIENT_SECRETS_FILE = "client_secret.json"
# スコープ(リクエストする権限のことです。)
# 今回はDriveAPIのスコープをリクエストします。
SCOPES = ["https://www.googleapis.com/auth/drive.file"]
app = Flask(__name__)
app.secret_key = os.urandom(24)
"""
ログインが終わった後に表示されるページです。
"""
@app.route("/")
def home():
message = "ログインに成功しました。"
return message
@app.route("/login")
def login():
"""
flowオブジェクトを作成します。
OAuth2.0を行う上で、その流れを管理してくれるオブジェクトです。
"""
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, SCOPES
)
"""
認証を終えた後にリダイレクトするURLを設定します。
"""
flow.redirect_uri = url_for("callback", _external=True)
"""
ユーザーに認証ページにリダイレクトするためのURLを取得します。
このURLにアクセスすると、よく見るGoogleの認証ページにリダイレクトされます。
"""
authorization_url, state = flow.authorization_url(
access_type="offline",
include_granted_scopes="true"
)
"""
セッションにstateを保存します。
このstateは、認証が終わった後に、
リクエストが正しいかどうかを確認するためのものです。
主にCSRF攻撃を防いだり、途中でのエラーなどを検知するために使われます。
"""
session["state"] = state
# ユーザーを認証ページにリダイレクトします。
return redirect(authorization_url)
@app.route("/callback")
def callback():
"""
リクエストが正しいかどうか、stateを参照し確認を行います。
そして、またflowオブジェクトを作成し、
サーバーが認可サーバーに対して、トークンをリクエストする準備をします。
"""
state = session["state"]
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, SCOPES, state=state
)
flow.redirect_uri = url_for("callback", _external=True)
authorization_response = request.url
"""
ここでアクセストークンを取得し、セッションに保存します。
"""
flow.fetch_token(authorization_response=authorization_response)
credentials = flow.credentials
session["credentials"] = credentials_to_dict(credentials)
return redirect(url_for("home"))
@app.route("/logout")
def logout():
"""
セッションを削除し、ログアウトします。
"""
session.clear()
return redirect(url_for("login"))
"""
credentialsオブジェクトを辞書型に変換します。
"""
def credentials_to_dict(credentials):
return {'token': credentials.token,
'refresh_token': credentials.refresh_token,
'token_uri': credentials.token_uri,
'client_id': credentials.client_id,
'client_secret': credentials.client_secret,
'scopes': credentials.scopes}
if __name__ == "__main__":
# httpでGoogle OAtuthが使えるようにするための変数を設定
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
# デバッグモードを有効にして、アプリケーションを起動
app.run(debug=True, host="localhost", port=4040)
環境
今回は、環境構築が不要なGoogle Colabratoryを使ってOAuth2.0のデモを作成したいと思います。と、思ったのですが、Colabだと実装が少し特殊になってしまうので、ローカル環境で実装します。
依存関係のインストールがマジでめんどくさいですが、既存のサービスへの実装のしやすさを重視します。
実装環境は、
・Mac OS 14.5
・Python 3.11.0
・Flask
・仮想環境はvenv
です。多分Webで使われる人が多いと思うので、Flaskで実装してみます。Djangoなど他のフレームワークを使っている場合でもビュー関数に、今回紹介するものを書けば動くと思います。(多分)
GCPでのセットアップ
OAuth2.0をアプリで行うには、クライアントシークレットが必要です。
Google Cloud Console(GCP)でプロジェクトを作成するか選択して、画面左上のメニューから、「APIとサービス」にすすみ、「認証情報」を押してクライアントシークレットをダウンロードしてきてください。
依存関係のインストール
PythonでOAuth2.0を行うためのライブラリです。
pip install --upgrade google-api-python-client
pip install --upgrade google-auth google-auth-oauthlib google-auth-httplib2
pip install --upgrade flask
初期化
まずは、Google OAuth2.0が実装できるように必要なモジュールをインポートし、Flaskの基本的な初期化と、スコープ(権限)の設定を行います。
# OAuth2.0に必要なライブラリです。
import google_auth_oauthlib.flow
from flask import Flask, render_template, session, url_for, redirect, request
import os
CLIENT_SECRETS_FILE = "client_secret.json"
SCOPES = ["https://www.googleapis.com/auth/drive.file"]
app = Flask(__name__)
app.secret_key = os.urandom(24)
flowオブジェクトの作成
flowオブジェクトというのは、認証URLの発行やアクセスが正当なものであるかの検証、さらにはアクセストークンのリクエストまで、行ってくれるものです。
@app.route("/login")
def login():
"""
flowオブジェクトを作成します。
OAuth2.0を行う上で、その流れを管理してくれるオブジェクトです。
"""
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, SCOPES
)
そのあとは、callback URL(コールバックURL)というものをflowオブジェクトに設定します。これを設定しておかないと、flowオブジェクトがログインし終わった後、どこにユーザーを誘導すればいいのかわからなくなるので、このように設定を行います。
その後、URLを生成します。このとき、”state”というものも一緒に生成されるのですが、これはこのサーバーからのリクエストであることを確認するためのものです。長くなるので、詳しい説明は省きますがCSRF対策の一環でもあります。
"""
認証を終えた後にリダイレクトするURLを設定します。
"""
flow.redirect_uri = url_for("callback", _external=True)
"""
ユーザーに認証ページにリダイレクトするためのURLを取得します。
このURLにアクセスすると、よく見るGoogleの認証ページにリダイレクトされます。
"""
authorization_url, state = flow.authorization_url(
access_type="offline",
include_granted_scopes="true"
)
session["state"] = state
ユーザーに認証URLを返す
return redirect(authorization_url)
flaskのredirectメソッドを使って、ユーザーをログインページに誘導します。こうすることで、以下のような見慣れた画面にユーザーをアクセスさせることができます。画像は一部違いますが。
flowオブジェクトを作成
「またflowオブジェクト?」と思った人もいると思います。このflowオブジェクトはアクセストークンの発行を行なってくれます。
以下のコードは、そのアクセストークンの発行の準備を行ってくれます。
"""
リクエストが正しいかどうか、stateを参照し確認を行います。
そして、またflowオブジェクトを作成し、
サーバーが認可サーバーに対して、トークンをリクエストする準備をします。
"""
state = session["state"]
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, SCOPES, state=state
)
flow.redirect_uri = url_for("callback", _external=True)
authorization_response = request.url
アクセストークンの発行の準備が終われば、以下のコードでアクセストークンが取得されます。
flow.fetch_token(authorization_response=authorization_response)
credentials = flow.credentials
session["credentials"] = credentials_to_dict(credentials)
そして、セッションにアクセストークンを保存し、Google APIのリクエスト時にセッションから、アクセストークンを取り出して、APIのリクエストを送ることができる、というわけです。
これで、OAuth2.0の大まかな実装は完了します。
ログアウトするときは、以下のコードでセッションの内容を全部削除することで、ログアウトを行うことができます。
@app.route("/logout")
def logout():
"""
セッションを削除し、ログアウトします。
"""
session.clear()
return redirect(url_for("login"))
まとめ
OAuth2.0は概念の理解に時間がかかる分野です。ですが、一度理解してしまうと楽に実装できるようになります。OAuth2.0は特にGoogleのようなAPIをたくさん提供しているようなサービスを利用するときは必ず必要になってきます。この記事がみなさんの実装の助けになると幸いです。
FAQ
Q1: OAuth 2.0とは何ですか?
A1: OAuth 2.0は、IDとパスワードを使わずにアプリケーションが特定のサービス(例:Googleスプレッドシート)にアクセスするための仕組みです。ユーザーの機密情報を直接扱わずに、安全にサービスにアクセスできるようにします。
Q2: OAuth 2.0の主な登場人物(サーバー)は何ですか?
A2: OAuth 2.0には主に3種類のサーバーが関係します:
- クライアントサーバー:私たちが構築するサーバー(WebアプリやバックエンドAPI)
- 認可サーバー:ユーザーの同意に基づいてアクセストークンを発行するサーバー
- リソースサーバー:実際のユーザーデータが保存されているサーバー(例:Googleスプレッドシート)
Q3: アクセストークンとは何ですか?
A3: アクセストークンは、IDとパスワードの代わりに使用される文字の羅列で、アプリケーションがユーザーのデータにアクセスするための「鍵」のような役割を果たします。
Q4: OAuth 2.0の基本的な流れは?
A4:
- ユーザーがアプリにデータ保存などの操作を行う
- アプリが認可サーバーにアクセストークンをリクエスト
- 認可サーバーがユーザーに許可を求める(ログイン画面表示)
- ユーザーが許可すると、認可サーバーがアプリにアクセストークンを発行
- アプリがアクセストークンを使ってリソースサーバーにアクセス
Q5: PythonでOAuth 2.0を実装するために必要なライブラリは?
A5: 主に以下のライブラリが必要です:
- google-api-python-client
- google-auth
- google-auth-oauthlib
- google-auth-httplib2
- Flask(Webアプリケーションフレームワークとして)
Q6: flowオブジェクトとは何ですか?
A6: flowオブジェクトは、OAuth 2.0の認証プロセスを管理するオブジェクトです。認証URLの発行、アクセスの検証、アクセストークンのリクエストなどを行います。
Q7: コールバックURLの設定は何のために必要ですか?
A7: コールバックURLは、ユーザーが認証を完了した後にリダイレクトされるURLです。これを設定することで、認証後のユーザーの遷移先を指定できます。
Q8: セッションにstateを保存する理由は?
A8: stateはリクエストの正当性を確認するために使用されます。主にCSRF(クロスサイトリクエストフォージェリ)攻撃を防ぐために重要です。
Q9: アクセストークンをセッションに保存する理由は?
A9: アクセストークンをセッションに保存することで、その後のAPI呼び出し時に簡単にトークンを取り出して使用できます。これにより、ユーザーが認証済みであることを維持できます。
Q10: ログアウト処理はどのように行いますか?
A10: ログアウト処理は、セッションの内容を全て削除することで行います。Flaskを使用している場合、session.clear()
メソッドを呼び出すことでセッションをクリアできます。
コメント