バックエンドに適した認証または認可の決定
James Reed
Infrastructure Engineer · Leapcell

はじめに
バックエンド開発の活気に満ちた世界では、ユーザーアクセスを安全に管理することが最優先事項です。洗練されたマイクロサービスアーキテクチャやモノリシックなエンタープライズアプリケーションを構築しているかどうかにかかわらず、「このユーザーは誰か?」と「このユーザーは何ができるか?」という疑問は必ず発生します。多くの開発者、そして経験豊富なアーキテクトでさえ、これら2つの異なる質問に答えるために設計されたメカニズムの間には、概念的な曖昧さをしばしば経験します。この混乱は、単純なソリューションの過剰なエンジニアリングから、さらに重要なことに、セキュリティ上の脆弱性を導入することまで、誤ったステップにつながることがよくあります。認証と認可の正確な役割、そして特にOpenID Connect(OIDC)とOAuth 2.0がこの状況にどのように適合するかを理解することは、単なる学術的なものではありません。それはあなたのバックエンドシステムのセキュリティ、スケーラビリティ、保守性に直接影響します。この記事は、これらの概念を明確にし、特定のバックエンドニーズに最も適したソリューションを選択するためのガイドを提供することを目的としています。
コアコンセプトの説明
OIDCとOAuth 2.0の詳細に入る前に、この議論の基礎となる基本的な用語を明確に理解しましょう。
- 認証(Authentication): ユーザーのIDを確認するプロセス。「あなたはあなたが言った通りであることを証明できますか?」アプリケーションにユーザー名とパスワードでログインするとき、あなたは認証を行っています。
 - 認可(Authorization): 認証されたユーザーまたはアプリケーションが実行できることを決定するプロセス。「何にアクセスまたは実行することが許可されていますか?」たとえば、認証されたユーザーは、他のユーザーのプロフィールを編集するのではなく、自分のプロフィールを表示することを許可される場合があります。
 - IDプロバイダー(IdP): ユーザーのID情報を生成、維持、管理し、他のアプリケーション(サービスプロバイダー)に認証サービスを提供するサービス。例としては、Google、Facebook、または企業のLDAPサーバーなどがあります。
 - リソースサーバー: 保護されたリソース(API、ユーザーデータなど)をホストするサーバー。
 - クライアントアプリケーション: ユーザーに代わって保護されたリソースにアクセスしたいアプリケーション。これは、Webアプリケーション、モバイルアプリ、または別のバックエンドサービスである可能性があります。
 
OAuth 2.0:認可フレームワーク
OAuth 2.0は、クライアントアプリケーションがリソース所有者(通常はユーザー)に代わって、リソース所有者の資格情報をクライアントに公開することなく、保護されたリソースへの「委任されたアクセス」を取得できるようにする認可フレームワークです。これはユーザー認証を直接扱ってはいません。代わりに、クライアントが保護されたリソースにアクセスするために使用されるアクセストークンを取得するための安全な方法を提供します。
OAuth 2.0の仕組み
OAuth 2.0のコアフロー、通常は承認コードグラントタイプは次のとおりです。
- クライアントが認可を要求: クライアントアプリケーション(例:あなたのバックエンド)は、特定の(スコープ)リソースへのアクセスを要求するために、ユーザーのブラウザを認可サーバーに誘導します。
 - ユーザーが認可を付与: 認可サーバーのインターフェイスで、ユーザーは認可サーバー(資格情報、例:ユーザー名/パスワード)で認証し、クライアントアプリケーションが要求されたリソースにアクセスすることを許可します。
 - 認可サーバーが承認コードを付けてリダイレクト: 認可サーバーは、一度だけ使用できる
authorization_codeを付けて、ユーザーのブラウザをクライアントアプリケーションにリダイレクトします。 - クライアントがコードをトークンと交換: クライアントアプリケーションは、機密性の高いクライアント資格情報と
authorization_codeを使用して、認可サーバーのトークンエンドポイントに直接リクエストを送信します。 - 認可サーバーがトークンを発行: 認可サーバーはリクエストを検証し、
access_token(およびオプションでrefresh_token)を発行します。 - クライアントが保護されたリソースにアクセス: クライアントアプリケーションは、
access_tokenを使用してリソースサーバーにリクエストを行います。 
OAuth 2.0を使用するタイミング
バックエンドアプリケーションが以下を必要とする場合にOAuth 2.0が必要です。
- ユーザーに代わってサードパーティAPIにアクセスする:たとえば、アプリケーションがユーザーのFacebookフィードに投稿したり、Googleカレンダーを表示したり、GitHubアカウントからデータをプルしたりしたい場合。このシナリオでは、あなたのバックエンドは「クライアントアプリケーション」であり、Facebook/Google/GitHubは「リソースサーバー」です。
 - サービス間のきめ細かなアクセス制御を有効にする:マイクロサービスアーキテクチャでは、あるサービスが別のサービスにアクセスする必要がある場合がありますが、特定のエンドポイントまたは特定の権限のみを持つ場合です。OAuth 2.0は、このようなサービス間認可を促進できます。
 
例:サードパーティAPIアクセスにOAuth 2.0を使用する(概念的)
あなたのバックエンドがPythonのFlaskで記述されており、ユーザーのGoogle Driveファイルにアクセスする必要があると想像してみましょう。
# これは非常に簡略化されており概念的です。実際の実際の実装では # Authlibやgoogle-auth-oauthlibのようなライブラリを使用し、stateやPKCEなどを処理します。 from flask import Flask, redirect, url_for, session, request import requests import os app = Flask(__name__) app.secret_key = os.urandom(24) # 強固で永続的なキーに置き換えてください GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID") GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET") GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth" GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token" GOOGLE_DRIVE_API_URL = "https://www.googleapis.com/drive/v3/files" REDIRECT_URI = "http://localhost:5000/callback" @app.route('/') def index(): if 'google_access_token' in session: return f"Hello, you\'re authenticated with Google. <a href='{url_for('list_drive_files')}'>List Drive Files</a>" return '<a href="/login_google">Login with Google</a>' @app.route('/login_google') def login_google(): params = { "client_id": GOOGLE_CLIENT_ID, "redirect_uri": REDIRECT_URI, "response_type": "code", "scope": "https://www.googleapis.com/auth/drive.readonly", # ドライブの読み取り専用アクセスを要求 "access_type": "offline", # リフレッシュトークンを取得するため "prompt": "consent" } auth_url = f"{GOOGLE_AUTH_URL}?{'&'.join([f'{k}={v}' for k,v in params.items()])}" return redirect(auth_url) @app.route('/callback') def callback(): code = request.args.get('code') if not code: return "Authorization code missing!", 400 token_payload = { "client_id": GOOGLE_CLIENT_ID, "client_secret": GOOGLE_CLIENT_SECRET, "code": code, "grant_type": "authorization_code", "redirect_uri": REDIRECT_URI } response = requests.post(GOOGLE_TOKEN_URL, data=token_payload) token_data = response.json() if 'access_token' in token_data: session['google_access_token'] = token_data['access_token'] # 長期アクセス用にリフレッシュトークンを保存することもできます # session['google_refresh_token'] = token_data['refresh_token'] return redirect(url_for('index')) return "Failed to get access token.", 500 @app.route('/drive_files') def list_drive_files(): access_token = session.get('google_access_token') if not access_token: return "Not authenticated with Google Drive.", 401 headers = { "Authorization": f"Bearer {access_token}" } response = requests.get(GOOGLE_DRIVE_API_URL + "?q='me' in owners", headers=headers) if response.status_code == 200: files = response.json().get('files', []) file_names = [f['name'] for f in files] return f"Your Google Drive files: {', '.join(file_names)}" else: return f"Error accessing Google Drive: {response.text}", response.status_code if __name__ == '__main__': app.run(debug=True)
この例では、FlaskバックエンドがOAuth 2.0クライアントとして機能します。ユーザーの認証をGoogleに委任し、ユーザーの許可を得て、GoogleDriveとやり取りするためのaccess_tokenを取得します。ユーザーはGoogleパスワードをあなたのアプリケーションに入力しません。
OpenID Connect(OIDC):OAuth 2.0の上に構築された認証
OIDCは、OAuth 2.0フレームワークの上に構築された認証レイヤーです。OAuth 2.0が認可(委任されたアクセス)を処理するのに対し、OIDCは認証を処理し、エンドユーザーのIDに関する検証可能な情報を提供します。目標を達成するためにOAuth 2.0フローを使用します。つまり、OAuth 2.0を理解していれば、OIDCを理解する半分は完了したことになります。
OIDCの仕組み
OIDCは新しい成果物、IDトークンを導入します。これは、ユーザーID、名前、メールアドレス、メールアドレスが検証されたかどうかなどのクレーム(アサーション)が含まれるJSON Web Token(JWT)です。OIDCフローは通常、次のようなものになります(OIDCの承認コードフローを使用)。
- クライアントが認証を要求: クライアントアプリケーションは、ユーザーをOIDCプロバイダー(OAuth 2.0認可サーバーでもある)にリダイレクトします。リクエストには、OIDCリクエストを示す
openidスコープと、その他の希望するスコープ(profile、emailなど)が含まれます。 - ユーザーが認証および同意: ユーザーはOIDCプロバイダーで認証し、クライアントアプリケーションにID情報を共有することに同意します。
 - 認可サーバーが認証コードを付けてリダイレクト: OIDCプロバイダーは、
authorization_codeを付けてユーザーをクライアントアプリケーションにリダイレクトします。 - クライアントがトークンと交換: クライアントアプリケーションは、
authorization_codeをクライアント資格情報と共にOIDCプロバイダーのトークンエンドポイントに送信します。 - OIDCプロバイダーがトークンを発行: OIDCプロバイダーは、
access_token、refresh_token(オプション)、そして最も重要な**id_token**を応答します。 - クライアントが
id_tokenを検証して使用: クライアントアプリケーションはid_tokenを検証(署名、発行者、対象者、有効期限を確認)して、ユーザーのIDを確認します。検証後、クライアントはid_tokenクレームからユーザー情報を抽出できます。 - クライアントが
access_tokenを認可に使用: 追加のリソースアクセスが要求された場合(openid以外のスコープ経由)、クライアントはaccess_tokenを使用して保護されたAPIを呼び出すことができます。 
OIDCを使用するタイミング
バックエンドアプリケーションが以下を必要とする場合にOIDCが必要です。
- ユーザーの認証: これが主なユースケースです。Google、GitHub、またはその他のOIDC準拠のIDを使用してユーザーをアプリケーションにログインさせる場合、OIDCがソリューションです。
 - シングルサインオン(SSO)の実装: OIDCはSSOの基盤です。ユーザーがOIDCプロバイダーで認証すると、再認証なしに同じプロバイダーに接続された複数のアプリケーションにシームレスにアクセスできます。
 - ユーザープロファイル情報の取得: 
id_tokenは、名前、メールアドレス、プロフィール画像などの基本的なユーザー属性を、IDプロバイダーから直接取得するための標準化された方法を提供します。 
例:OIDCを使用してユーザー認証を行う(概念的)
バックエンドがGoogleのOIDC機能を通じてユーザーを認証し、その後セッションを管理する必要があると仮定しましょう。
# 再び、非常に簡略化されています。Authlibのようなライブラリはこれを大幅に簡素化します。 from flask import Flask, redirect, url_for, session, request, jsonify import requests import os import jwt # IDトークン検証のためのPyJWT app = Flask(__name__) app.secret_key = os.urandom(24) # 強固で永続的なキーに置き換えてください GOOGLE_OIDC_CLIENT_ID = os.environ.get("GOOGLE_OIDC_CLIENT_ID") GOOGLE_OIDC_CLIENT_SECRET = os.environ.get("GOOGLE_OIDC_CLIENT_SECRET") GOOGLE_DISCOVERY_URL = "https://accounts.google.com/.well-known/openid-configuration" REDIRECT_URI = "http://localhost:5000/oidc_callback" # OIDCメタデータ(例:authorization_endpoint、token_endpoint、jwks_uri)を取得 # 実際のアプリでは、これはキャッシュされ、リフレッシュされます。 response = requests.get(GOOGLE_DISCOVERY_URL) OIDC_METADATA = response.json() @app.route('/') def index(): if 'user_info' in session: user = session['user_info'] return f"Hello, {user.get('name', user.get('sub'))}! <a href='/logout'>Logout</a>" return '<a href="/login_oidc">Login with Google (OIDC)</a>' @app.route('/login_oidc') def login_oidc(): params = { "client_id": GOOGLE_OIDC_CLIENT_ID, "redirect_uri": REDIRECT_URI, "response_type": "code", "scope": "openid profile email", # OIDCのための重要な`openid`スコープ "access_type": "offline", "prompt": "consent" } auth_url = f"{OIDC_METADATA['authorization_endpoint']}?{'&'.join([f'{k}={v}' for k,v in params.items()])}" return redirect(auth_url) @app.route('/oidc_callback') def oidc_callback(): code = request.args.get('code') if not code: return "Authorization code missing!", 400 token_payload = { "client_id": GOOGLE_OIDC_CLIENT_ID, "client_secret": GOOGLE_OIDC_CLIENT_SECRET, "code": code, "grant_type": "authorization_code", "redirect_uri": REDIRECT_URI } response = requests.post(OIDC_METADATA['token_endpoint'], data=token_payload) token_data = response.json() if 'id_token' in token_data: id_token = token_data['id_token'] # --- IDトークン検証(重要なセキュリティステップ) --- # 1. プロバイダーからJWKSを取得して署名を検証する jwks_response = requests.get(OIDC_METADATA['jwks_uri']) jwks = jwks_response.json() # 実際のアプリでは、適切なキー取得と検証を備えた堅牢なJWTライブラリ(PyJWTなど)を使用します。 # これは簡略化された例です。 try: # jwksから正しいキーを取得する必要があります header = jwt.get_unverified_header(id_token) key = None for jwk in jwks['keys']: if jwk['kid'] == header['kid']: key = jwk break if not key: raise ValueError("Matching JWK not found for KID") public_key = jwt.decode(id_token, key, algorithms=[header['alg']], audience=GOOGLE_OIDC_CLIENT_ID, issuer=OIDC_METADATA['issuer'], options={"verify_signature": True}) # 追加の検証(例:nonce、有効期限、'azp'クレーム)はここで行われます。 session['user_info'] = public_key return redirect(url_for('index')) except jwt.exceptions.PyJWTError as e: app.logger.error(f"ID Token verification failed: {e}") return "ID Token verification failed.", 401 return "Failed to get ID token.", 500 @app.route('/logout') def logout(): session.pop('user_info', None) # 保存されていた場合、オプションでアクセス/リフレッシュトークンを無効にする return redirect(url_for('index')) if __name__ == '__main__': app.run(debug=True)
ここでは、FlaskバックエンドがGoogle経由でユーザーを認証するためにOIDCを使用します。受信したid_tokenは検証され、その中のクレームはアプリケーションでユーザーセッションを確立するために使用されます。あなたのバックエンドは、ユーザーが誰であるかを知っています。access_token(これも受信)は、追加の権限が要求された場合にGoogle APIを呼び出すために使用できますが、id_tokenのみがIDを確認します。
認証対認可:明確な区別
重要なのは、**OIDCは認証(ユーザーは誰か?)**であり、**OAuth 2.0は認可(彼らは何ができるか?)**であるということです。
- 主な目標が、外部IDプロバイダー(Google、Facebook、Okta、Auth0など)を使用して、ユーザーをあなたのアプリケーションにログインさせることである場合、OIDCが必要です。
id_tokenを使用してIDを確認します。 - 主な目標が、アプリケーションがユーザー(または別のサービス)に代わって別のアプリケーションの保護されたリソースに安全にアクセスすることである場合、OAuth 2.0が必要です。
access_tokenを使用して認可されたリクエストを行います。 
両方を一緒に使用することは一般的です。たとえば、ユーザーはあなたのアプリケーションにログインし(OIDCによる認証)、その後あなたのアプリケーションはGoogleカレンダーにアクセスする必要があるかもしれません(OAuth 2.0による認可)。これらはすべて、Googleとの単一のやり取りを通じて開始されます。最初のOIDCフローは、id_tokenとaccess_tokenの両方(適切なスコープが要求されたと仮定)を返します。
結論
バックエンドのOpenID ConnectとOAuth 2.0のどちらを選択するかは、根本的な質問にかかっています。ユーザーが誰であるかを検証しようとしていますか、または彼らが何ができるかを管理しようとしていますか? OIDCはOAuth 2.0を拡張して堅牢な認証機能を提供し、ユーザーIDを主張するための標準化された方法を提供します。Conversely、OAuth 2.0は、資格情報を共有することなく保護されたリソースへの安全なアクセスを可能にする、委任された認可のための業界標準のままです。ユーザーログインとID情報にはOIDCを使用してください。外部APIへの委任されたアクセスにはOAuth 2.0を使用してください。