1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Node.js】Google認証でOAuth 2.0とOpenID Connect学んでみた

Last updated at Posted at 2024-04-25

OAuth 2.0およびOpenID Connectの勉強のため、Google OAuth 2.0 API を使って、Node.jsでGoogle認証を行うクライアントアプリを実装してみました。Googleの認可/Idpサーバを使用し、トークンエンドポイントからアクセストークンIDトークンを取得するところまで実装しています。

この記事では、OAuth 2.0 の認可コードフローに対応したクライアントアプリを実装しながら学んだことを整理しておきます。

シーケンス図

今回実装した、OpenID Connectのクライアントアプリのシーケンス図です。
基本的なOAuth 2.0の「認可コードフロー」になっています。

背景が薄い緑の部分が認可リクエストに関する処理、薄い水色がトークンリクエストに関する処理になります。

学習メモ

OAuth 2.0 クライアント設定

OAuth 2.0 ではクライアントアプリを識別するためのクライアント情報の登録が必要となります。Google OAuth 2.0 APIにおけるクライアント設定はGoogle API Consoleでクライアント情報を設定します。

クライアントを設定するとclient_idclient_secretが払い出されます。この情報は主に認可サーバでのクライアント認証で使用されます。

また、認可サーバからのリダイレクト先として承認するURIをクライアントへ設定する必要があります。今回のリダイレクト先URIはhttp://localhost:8080/callbackとしました。

Node.js
const REDIRECT_URI = "http://localhost:8080/callback"
const CLIENT_ID = "クライアントID"
const CLIENT_SECRET = "シークレットID"

認可リクエスト

認可リクエストは認可エンドポイントへ下記のようなリクエストを送信します。

  GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
      &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
  Host: server.example.com

Googleはディスカバリドキュメント(.well-known/openid-configuration)を公開しているので、この中のauthorization_endpointパラメータから認可エンドポイントURIを取得します。

また、認可リクエストはユーザーエージェントを介して送信する必要があるため、認可リクエストURLへのリダイレクトをブラウザに返します。

Node.js
  // ディスカバリドキュメントから認可エンドポイントを取得する。
  const resp = await fetch(DISCOVERY_ENDPOINT);
  const provider_cfg = await resp.json();
  const authorization_endpoint = provider_cfg['authorization_endpoint']

  // 認可リクエスト用のパラメータの設定
  const params = {
    client_id: CLIENT_ID,
    response_type: "code",
    scope: ["openid email profile"],
    redirect_uri: REDIRECT_URI,
    prompt: "select_account",
  };

  const query = new URLSearchParams(params);
  const request_url = authorization_endpoint + `?${query}`

  // ブラウザにリダイレクトを返す
  response.writeHead(302, { 'Location': request_url })
  response.end()

リクエストで指定するパラメータについては下記を参考にしました。

パラメータ 必須 設定値例 備考
client_id "クライアントID" クライアント設定で払い出されたクライアント識別子を指定
response_type "code" 認可コードフローの場合は"code"を指定
scope - "openid" "email" "profile" スコープはスペース区切りで指定。値の順序は考慮しない。
redirect_uri - "http://localhost:8080/callback" 絶対URIで指定。クライアントに設定した「承認済みリダイレクトURI」と完全一致しなければならない。
state - "xyz" 設定が推奨されている。認可リクエストに含めたstateの値と後述の認可レスポンスで返されたstateの値が同一であることを確認することでCSRF対策が行える。

認可レスポンス

認可リクエストを送信後、ユーザーの認証+認可操作が正常に完了すると、下記のような認可レスポンスがブラウザに返されます。

  HTTP/1.1 302 Found
  Location: http://localhost:8080/callback?code=SplxlOBeZQQYbYS6WxSbIA
            &state=xyz

リダイレクト先としては認可リクエストでredirect_uriに指定したURIが設定され、クエリパラメータにcodeが含まれていることがわかります。

このcodeが認可サーバから返された認可コードです。認可コードはユーザーの認証・認可が正常に実施されたことを表しており、このcodeの値をトークンエンドポイントのリクエストパラメータに含めることで、アクセストークンと交換することができます。

レスポンスの各パラメータ項目の説明は下記となります。

パラメータ 必須 設定値例 備考
code SplxlOBeZQQYbYS6WxSbIA アクセストークンと交換するための短命なコード。有効期限は最大でも10分が推奨されている。
state - xyz 認可リクエストでstateが含まれていた場合、必須。

トークンリクエスト

認可レスポンスで受け取った認可コードをトークンエンドポイント送信します。

  POST /token HTTP/1.1
  Host: server.example.com
  Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
  Content-Type: application/x-www-form-urlencoded

  grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
  &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

Googleのディスカバリドキュメントのtoken_endpointパラメータからトークンエンドポイントURIを取得します。

Node.js
  //リクエストパラメータから認可コードを取得する
  const query = url.parse(request.url).query;
  const code = querystring.parse(query).code;

  // ディスカバリドキュメントからトークンエンドポイントを取得する。
  const resp = await fetch(DISCOVERY_ENDPOINT);
  const provider_cfg = await resp.json();
  const authorization_endpoint = provider_cfg['token_endpoint']

  // リクエストボディの設定
  const body = new URLSearchParams({
    "code": code,
    "client_id": CLIENT_ID,
    "client_secret": CLIENT_SECRET,
    "redirect_uri": REDIRECT_URI,
    "grant_type": "authorization_code",
  });

  const resp_token = await fetch(authorization_endpoint, {
    method: "POST",
    headers:{
      "Content-Type":"application/x-www-form-urlencoded",
    },
    body,
  })
  const token = await resp_token.json();

リクエストに含める各パラメータの説明は下記を参考にしました。

パラメータ 必須 設定値例 備考
grant_type "authorization_code" 認可コードフローであることを表す
code "xyz" 認可サーバーから受け取った認可コード
redirect_uri - "http://localhost:8080/callback" 認可リクエストでredirect_uriが含まれていた場合、必須。
client_id - "クライアントID" クライアント認証済みの場合、指定は必須ではない。

トークンレスポンス

トークンリクエストを送信後、下記のような認可レスポンスがJSON形式で返されます。これで、アクセストークンとIDトークンの取得ができました。

  HTTP/1.1 200 OK
  Content-Type: application/json;charset=UTF-8
  Cache-Control: no-store
  Pragma: no-cache

{
  access_token: 'ya29.〜',
  expires_in: 3599,
  scope: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid',
  token_type: 'Bearer',
  id_token: 'eyJ〜'
}

まだまだOAuth 2.0 やOpenID Connectについて学習途中で、誤った記載があるかもですが、学んだことを整理しました。
(もし、誤りがある場合は指摘いただけると助かります)

以上です。

参考記事

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?