認証・認可の仕組みを完全理解|OAuth2.0・OpenID Connect・AWS Cognitoを初心者から実務レベルまで解説

AWS

「ログイン機能を実装したいけど、OAuth 2.0 って何をするものなの?」「OpenID Connect と OAuth の違いが分からない」「AWS Cognito を使おうとしたら User Pool と Identity Pool があって混乱した」――こういった悩みを持つエンジニアは非常に多いです。

この記事では、認証・認可の基礎概念から出発し、OAuth 2.0OpenID Connect(OIDC)AWS Cognito の仕組みを図解つきで徹底解説します。読み終えた後には「どのプロトコルを・いつ・なぜ使うべきか」が判断できる実務レベルの知識が身につきます。対象読者はWebアプリ開発経験があり、認証周りの実装を担当する予定のエンジニアです。


認証(Authentication)と認可(Authorization)の違い

認証vs認可 および OAuth 2.0 vs OpenID Connect の比較図

セキュリティを学ぶうえで最初に押さえるべきなのが、認証(Authentication)認可(Authorization)の違いです。この2つは混同されがちですが、役割がまったく異なります。

用語英語問いかけ具体例
認証Authenticationあなたは誰ですか?パスポートで本人確認
認可Authorizationあなたは何をしてよいですか?チェックした部屋のみ入室可

ホテルのチェックインで例えると分かりやすいです。フロントでパスポートを提示して「この人は確かに予約者本人だ」と確認されるのが認証です。その後、割り当てられた部屋のカードキーが渡されて「この部屋にだけ入れる」とアクセス範囲を制限するのが認可です。

重要:認証なしに認可は成立しません。まず「誰であるか」を確認(認証)し、その後「何ができるか」を制御する(認可)という順序が基本です。また、OAuth 2.0 は本来「認可」のフレームワークであり、認証は担いません。この区別は後ほど OpenID Connect を理解する際に非常に重要になります。


OAuth 2.0 とは何か

OAuth 2.0 は、あるサービスが別のサービスのリソースへアクセスする権限を、ユーザーのパスワードを渡さずに委任するための認可フレームワークです。RFC 6749 で標準化されています。

身近な例で言えば、「Twitter アカウントでログイン」や「Google カレンダーと連携する」機能がすべて OAuth 2.0 を使っています。ユーザーは Google のパスワードを外部アプリに教えることなく、Google カレンダーの読み取り権限だけを安全に渡せます。


OAuth 2.0 の4つの登場人物

ロール英語名説明具体例
リソースオーナーResource Owner保護されたリソースの所有者エンドユーザー本人
クライアントClientリソースにアクセスしたいアプリスマートフォンアプリ・SPA
認可サーバーAuthorization Serverアクセストークンを発行するサーバーGoogle / Auth0 / Cognito
リソースサーバーResource Server保護されたリソースを持つサーバーGoogle Calendar API


OAuth 2.0 の4つのグラントタイプ

OAuth 2.0の「4つのグラントタイプ」は、どの方法でアクセストークンを取得するか(認可のフロー)の違いです。

前提👇
OAuth 2.0 は「ユーザーの代わりに他のサービスへアクセスするための仕組み」です。これを踏まえて4つ2つのグランドタイプを確認していきます。

  • 認可コードフロー(Authorization Code Grant):最もセキュアで推奨される方法。サーバーサイドアプリや PKCE 付きのSPA・モバイルアプリで使用
  • クライアントクレデンシャルフロー(Client Credentials Grant):ユーザーが介在しないマシン間通信(M2M)向け。バックエンドのバッチ処理やマイクロサービス間通信で使用
  • インプリシットフロー(Implicit Grant):ブラウザ上でアクセストークンを直接取得する旧来の方法。現在は非推奨(PKCE 付き認可コードフローを使うべき)
  • リソースオーナーパスワードクレデンシャルフロー(ROPC):ユーザーのID/パスワードをクライアントに直接渡す。信頼できるアプリのみ使用可・原則非推奨

現在(2024年以降)の主流は 認可コードフロー + PKCE です。SPAやモバイルアプリなど、クライアントシークレットを安全に保管できない環境では、インプリシットフローではなく必ずPKCEを使用してください。


認可コードフロー(Authorization Code Flow)の仕組み

OAuth 2.0 認可コードフロー + PKCE のシーケンス図

認可コードフローは、ユーザー・クライアントアプリ・認可サーバー・リソースサーバーの4者が登場するフローです。以下の6ステップで進みます。

  1. ユーザーがログインボタンをクリックし、クライアントアプリにリクエストを送る
  2. クライアントが /authorize エンドポイントへリダイレクト(PKCE使用時は code_challenge を付与)
  3. 認可サーバーがログイン画面を表示し、ユーザーがID/パスワードを入力して認証
  4. 認可サーバーが短命の認可コード(Authorization Code)をコールバックURLへ返却
  5. クライアントが認可コードと code_verifier を /token エンドポイントに送付し、アクセストークン・IDトークン・リフレッシュトークンを取得
  6. 取得したアクセストークンを使って、リソースサーバーのAPIを呼び出す

ポイントはアクセストークンをブラウザのURLに直接露出させない点です。認可コードは一時的なもので、バックエンドのサーバー間通信でのみトークンと交換されます。これがインプリシットフローより安全な理由です。


PKCE(Proof Key for Code Exchange)とは

PKCE(ピクシー)は、認可コードフローの弱点である認可コード横取り攻撃を防ぐための拡張仕様です。SPAやモバイルアプリなど、クライアントシークレットを安全に保管できない環境で必須です。

# 1. code_verifier の生成(ランダムな高エントロピー文字列)
code_verifier=el6UJ6cSfyDJwOCo2bqVyOE33M21mQDLZvQ5ujMdk

# 2. code_challenge の生成(SHA-256ハッシュをBase64URLエンコード)
code_challenge=Eq4yyx7ALQHto1gbEnwf7jsNxTVy7WuvI5choD2C4SY

echo "code_verifier: "
echo "code_challenge: "

認可サーバーとの接続開始時に、クライアント側でランダムな文字列(code_verifier)を作成します。この文字列をハッシュ化した値(code_challenge)とハッシュ化に使用したハッシュ関数(code_challenge_method)を認可サーバーのエンドポイントへリダイレクト時に送信します。

認証成功後、認可サーバーから認可コードがクライアント側に送られます。クライアントはその認可コードとランダムな文字列(code_verifier)を認可サーバーに送ります。

認可サーバーはその code_verifier をハッシュ化し、事前に受け取っていた code_challenge と一致するかを確かめます。整合性が合えば、認可サーバーはクライアントにアクセストークンを発行します。


OpenID Connect(OIDC)とは何か

OpenID Connect(OIDC)は、OAuth 2.0 の上に「認証」レイヤーを追加したプロトコルです。OAuth 2.0 単体では「誰がログインしているか」が分からないという問題を解決します。

OIDC が追加した最大の要素がIDトークン(ID Token)です。IDトークンは JWT(JSON Web Token)形式で、以下の情報が含まれています。

// IDトークン(JWT)のペイロード例
{
  "iss": "https://accounts.google.com",   // 発行者(Issuer)
  "sub": "1234567890",                     // ユーザーの一意識別子(Subject)
  "aud": "your-client-id",               // 受信者(Audience)
  "exp": 1700000000,                       // 有効期限(Unix時刻)
  "iat": 1699996400,                       // 発行時刻
  "email": "user@example.com",
  "name": "山田 太郎"
}

IDトークンは署名(RS256等)されているため改ざんを検知できます。アプリはIDトークンの sub クレームをユーザーの識別子として使い、email や name などのプロフィール情報を取得できます。これにより、シングルサインオン(SSO)が実現できます。


JWT(JSON Web Token)の構造

JWT は OAuth 2.0 や OIDC で広く使われるトークン形式です。ドット(.)で区切られた3つのパーツで構成されます。

# JWT の構造(ヘッダー.ペイロード.署名)
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLWlkLTEyMyIsInNjb3BlIjoib3BlbmlkIGVtYWlsIn0.signature

# ヘッダー(Base64URLデコード後)
{
  "alg": "RS256",
  "typ": "JWT"
}

# ペイロード(Base64URLデコード後)
{
  "sub": "user-id-123",
  "scope": "openid email profile",
  "exp": 1700000000
}

# 署名 = RSA_sign(header + "." + payload, 秘密鍵)

JWT のペイロードは Base64URL でエンコードされているだけで、暗号化はされていません。センシティブな情報(パスワード等)はJWTのペイロードに含めないでください。署名により改ざん検知はできますが、内容は誰でもデコードして読めます。

JWTについてはこちらを参考にしてみてください


AWS Cognitoの全体像

AWS Cognito アーキテクチャ図(User Pool と Identity Pool の関係)

AWS Cognito は、AWSが提供するフルマネージドの認証・認可サービスです。大きく2つのコンポーネントで構成されています。

コンポーネント役割発行するもの
User Pool(ユーザープール)認証(ユーザーディレクトリ)JWT(IDトークン・アクセストークン・リフレッシュトークン)
Identity Pool(IDプール)認可(AWSリソースアクセス)一時的なAWS認証情報(アクセスキー・シークレット・セッショントークン)


User Pool(ユーザープール)の機能と使い方

User Pool はユーザー管理と認証を担うコンポーネントです。主な機能は以下のとおりです。

  • ユーザーのサインアップ・サインイン機能(メール/SMS認証付き)
  • MFA(多要素認証)サポート
  • パスワードポリシーの設定(最小文字数・複雑さなど)
  • ソーシャルログイン連携(Google / Facebook / Amazon / Apple)
  • Lambda トリガーによるカスタム認証フロー
  • 認証成功時に JWT(IDトークン・アクセストークン・リフレッシュトークン)を発行

User Pool で発行された JWT は、そのまま自前の API Gateway や Lambda 関数の認証に使えます。API Gateway のオーソライザーに Cognito User Pool を設定するだけで、JWT の検証を自動化できます。

import boto3
from botocore.exceptions import ClientError

def cognito_signin(username: str, password: str) -> dict:
    """Cognitoユーザープールへのサインイン"""
    client = boto3.client('cognito-idp', region_name='ap-northeast-1')
    
    try:
        response = client.initiate_auth(
            AuthFlow='USER_PASSWORD_AUTH',
            AuthParameters={
                'USERNAME': username,
                'PASSWORD': password,
            },
            ClientId='YOUR_APP_CLIENT_ID'
        )
        tokens = response['AuthenticationResult']
        return {
            'id_token': tokens['IdToken'],        # ユーザー情報付きJWT
            'access_token': tokens['AccessToken'], # API呼び出し用JWT
            'refresh_token': tokens['RefreshToken'] # 更新用トークン
        }
    except ClientError as e:
        print(f"認証エラー: {e.response['Error']['Message']}")
        raise


Identity Pool(IDプール)の機能と使い方

Identity Pool は、認証済みユーザー(または未認証ゲスト)に対して一時的なAWS認証情報を発行するコンポーネントです。主な用途はモバイルアプリやSPAから直接AWSサービス(S3・DynamoDB・Lambda等)にアクセスする場合です。

  1. User Pool で認証して JWT(IDトークン)を取得
  2. IDトークンを Identity Pool に送付
  3. Identity Pool がトークンを検証し、対応する IAM ロールにマッピング
  4. STS(Security Token Service)経由で一時的な AWS 認証情報(アクセスキー・シークレット・セッショントークン)を発行
  5. 発行された認証情報で S3 や DynamoDB に直接アクセス
import boto3

def get_aws_credentials_from_cognito(id_token: str) -> dict:
    """CognitoIDプールから一時的AWS認証情報を取得"""
    identity_client = boto3.client('cognito-identity', region_name='ap-northeast-1')
    
    USER_POOL_ID = 'ap-northeast-1_XXXXXXXXX'
    IDENTITY_POOL_ID = 'ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
    
    # 1. IdentityIDを取得
    id_response = identity_client.get_id(
        IdentityPoolId=IDENTITY_POOL_ID,
        Logins={
            f'cognito-idp.ap-northeast-1.amazonaws.com/{USER_POOL_ID}': id_token
        }
    )
    identity_id = id_response['IdentityId']
    
    # 2. 一時的AWS認証情報を取得
    creds_response = identity_client.get_credentials_for_identity(
        IdentityId=identity_id,
        Logins={
            f'cognito-idp.ap-northeast-1.amazonaws.com/{USER_POOL_ID}': id_token
        }
    )
    return creds_response['Credentials']


User Pool と Identity Pool の使い分け

ユースケース必要なコンポーネント
API Gateway + Lambda でバックエンドAPIを保護したいUser Pool のみ(JWTオーソライザー)
SPAやモバイルアプリから直接 S3 にファイルをアップロードしたいUser Pool + Identity Pool
ゲストユーザー(未認証)にも限定的なAWSアクセスを許可したいIdentity Pool のみ(匿名認証)
Google / Facebook でのソーシャルログインに対応したいUser Pool(フェデレーション機能)


Cognitoを使ったサーバーサイドでのJWT検証方法

User Pool から発行された JWT を自前のサーバーで検証する場合、Cognito が公開している JWK(JSON Web Key Set)を使います。Cognito の公開鍵 URL は以下の形式です。

# CognitoのJWK(公開鍵)エンドポイント

https://cognito-idp.
{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json # 例
https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX/.well-known/jwks.json
import jwt
from jwt.algorithms import RSAAlgorithm
import requests
import json

def verify_cognito_jwt(token: str, user_pool_id: str, client_id: str, region: str) -> dict:
    """Cognito JWTトークンの検証"""
    # JWKエンドポイントから公開鍵を取得
    jwks_url = f"https://cognito-idp.{region}.amazonaws.com/{user_pool_id}/.well-known/jwks.json"
    jwks_response = requests.get(jwks_url)
    jwks = jwks_response.json()
    
    # JWTヘッダーからkidを取得
    unverified_header = jwt.get_unverified_header(token)
    kid = unverified_header['kid']
    
    # kidに対応する公開鍵を探す
    public_key = None
    for key in jwks['keys']:
        if key['kid'] == kid:
            public_key = RSAAlgorithm.from_jwk(json.dumps(key))
            break
    
    if not public_key:
        raise ValueError("公開鍵が見つかりません")
    
    # JWTを検証してペイロードを返す
    payload = jwt.decode(
        token,
        public_key,
        algorithms=['RS256'],
        audience=client_id
    )
    return payload


認証・認可設計の実務チェックリスト

  • フロー選択:SPAやモバイルアプリには認可コードフロー + PKCE を使用する(インプリシットフローは禁止)
  • トークン有効期限:アクセストークンは短命(15〜60分)に設定し、リフレッシュトークンで更新する
  • スコープ最小化:アプリが必要とする最小限のスコープ(権限)のみを要求する
  • JWTの格納場所:ブラウザでは localStorage より httpOnly Cookie が安全(XSS対策)
  • HTTPS必須:すべての認証フローは HTTPS 上で実行する
  • state パラメータ:CSRF攻撃を防ぐため、認可リクエストには必ず state パラメータを付与して検証する
  • トークンの失効管理:ログアウト時はリフレッシュトークンを失効させる


まとめ

  • 認証(Authentication)は「誰であるか」の確認、認可(Authorization)は「何をしてよいか」の制御
  • OAuth 2.0 は認可のフレームワークであり、アクセストークンを発行してリソースアクセスを委任する
  • OpenID Connect は OAuth 2.0 を拡張した認証プロトコルで、IDトークン(JWT)によってユーザー識別とSSO を実現する
  • 現在の推奨フローは認可コードフロー + PKCE。インプリシットフローや ROPC は非推奨
  • AWS Cognito の User Pool は認証担当(JWT発行)、Identity Pool は認可担当(一時的AWS認証情報の発行)
  • 自前のAPIを保護するだけなら User Pool の JWT オーソライザーで十分。AWSサービスに直接アクセスする場合は Identity Pool も使う

認証・認可は「とりあえず動けばいい」ではなく、セキュリティ上の設計ミスが重大な脆弱性に直結する領域です。この記事で学んだ概念を土台に、実際のプロダクトでも正しいフローと設定を選択できるエンジニアを目指してください。


参考リソース

コメント

タイトルとURLをコピーしました